85.1.3. pepsi-httpd¶
serve MTA-STS policy, Web Key Directory, the Outlook add-in and metrics
- Section du manuel:
1
85.1.3.1.1. Nom¶
pepsi-httpd - serveur HTTP/HTTPS pour MTA-STS, le Web Key Directory, le module complémentaire Outlook et les métriques.
85.1.3.1.2. Synopsis¶
pepsi-httpd [GLOBAL-OPTIONS] serve
pepsi-httpd [GLOBAL-OPTIONS] prune
85.1.3.1.3. Description¶
pepsi-httpd est le serveur HTTP/HTTPS de Pepsi. Il lie un ou plusieurs listeners et achemine chaque requête à travers une table de dispatch générique, en mettant en correspondance par méthode HTTP et forme d’URL : le chemin d’une route est une liste de segments, chacun étant un littéral, une capture nommée d’un segment, ou une capture de tout le reste. Les routes sont essayées dans l’ordre d’enregistrement et la première dont la méthode et le chemin correspondent l’emporte. D’autres points de terminaison s’ajoutent comme de simples entrées de table.
Chaque section [pepsi-httpd-listener-<name>] lie une socket — SERVE = tcp (BIND_TO/PORT, port 443 par défaut), unix (UNIXPATH) ou systemd (activation par socket) — avec MODE = plain ou tls. Un listener TLS sélectionne son certificat par connexion d’après le nom d’hôte SNI du client : chaque section [pepsi-httpd-cert-<name>] liste un ou plusieurs noms d’hôte SNI avec une paire TLS_CERT/TLS_KEY, et le TLS_CERT/TLS_KEY propre au listener (le cas échéant) est le repli pour les connexions qui ne présentent aucune correspondance SNI.
Lorsqu’un autre serveur web possède déjà les ports 80/443, pepsi-setup(1) configure à la place un déploiement en reverse-proxy. Le listener reste activé par socket (SERVE = systemd) mais en HTTP en clair (MODE = plain) ; le basculement se fait plutôt dans l’unité pepsi-httpd.socket : pepsi-setup dépose un drop-in de redéfinition (/etc/systemd/system/pepsi-httpd.socket.d/10-reverse-proxy.conf) qui réinitialise le ListenStream=443 livré et le relie à une socket UNIX à /run/pepsi/httpd.sock (mode 0660, appartenant au groupe propre du serveur frontal — www-data là où ce groupe existe, sinon nginx/apache/httpd), de sorte que systemd passe le fd de cette socket à pepsi-httpd exactement comme il le ferait pour la socket du port 443. Le serveur frontal termine le TLS et lui transfère les requêtes mta-sts.<domain> ; dans ce mode, pepsi-httpd ne détient aucun certificat propre. Supprimer le drop-in et exécuter systemctl daemon-reload rétablit la liaison directe :443. Voir pepsi-setup(1) et --no-reverse-proxy.
85.1.3.1.4. Points de terminaison¶
- GET /.well-known/mta-sts.txt
Renvoie la politique MTA-STS (RFC 8461), construite à partir des options
MTA_STS_*de[pepsi]et duHOSTNAMEde[pepsi-ingress](lemx). La politique n’est servie que lorsque leHostde la requête estmta-sts.<domain>pour un domaine deACCEPTED_DOMAINS; tout autre hôte (ouMTA_STS_MODE = none) donne404. La servir ici évite d’héberger le fichier de politique à la main ; pepsi-setup(1) imprime l’enregistrement TXT_mta-stsà publier dans le DNS.- GET /mail/config-v1.1.xml
- GET /.well-known/autoconfig/mail/config-v1.1.xml
Renvoie le document d”autoconfiguration de compte de courrier (draft-ietf-mailmaint-autoconfig) : le XML qu’un client de messagerie récupère, ne connaissant que l’adresse de l’utilisateur, pour découvrir quels serveurs employer, sur quels ports, sous quelle sécurité de transport et avec quelle authentification.
Ce sont les deux premiers échelons de la chaîne de repli que définit le brouillon, et le
Hostchoisit entre eux : le premier est servi lorsque leHostde la requête estautoconfig.<domain>(l’échelon obligatoire, et celui que les clients essaient en premier), le second lorsque c’est<domain>lui-même. Dans les deux cas, DOMAIN doit figurer dansACCEPTED_DOMAINS; tout autre hôte donne404. Le paramètre facultatif?emailaddress=du brouillon est accepté et ignoré — le document nomme%EMAILADDRESS%, l’espace réservé que le client substitue, de sorte qu’aucun texte contrôlé par la requête n’est jamais interpolé dans la réponse.La réponse est
text/xml; charset=utf-8, cachable une heure, et — comme l’exige le brouillon — publique : elle ne porte aucune authentification, car un client doit la lire avant de pouvoir savoir comment s’authentifier.Le document est construit depuis
[pepsi-autoconfig], et le point de terminaison répond404tant que cette section ne nomme pas au moins un serveur entrant (IMAP_HOSTouPOP3_HOST). Le serveur sortant est dérivé du[pepsi-ingress-listener-*]marquéSUBMISSION = yes, sauf siSMTP_HOSTle redéfinit. La publication exige un enregistrement DNSautoconfig.<domain>et un certificat qui le couvre ; voir pepsi.conf(5).- GET /.well-known/openpgpkey/hu/HASH
- GET /.well-known/openpgpkey/policy
- GET /.well-known/openpgpkey/DOMAIN/hu/HASH
- GET /.well-known/openpgpkey/DOMAIN/policy
Le Web Key Directory (draft-koch-openpgp-webkey-service), dans sa forme directe (servie sous le domaine lui-même) comme dans sa forme avancée (servie sous
openpgpkey.<domain>, avec le domaine répété dans le chemin). HASH est le SHA-1 en z-base-32 de la partie locale mise en minuscules, stockée et indexée à côté du domaine, de sorte qu’une requête coûte une consultation indexée.Une clé n’est renvoyée que pour un domaine figurant dans
ACCEPTED_DOMAINSet seulement depuis une identitépublished,activeet OpenPGP. La réponse est la clé publique transférable binaire — non blindée — avecContent-Type: application/octet-streametAccess-Control-Allow-Origin: *comme l’exige la spécification. Un condensat inconnu donne404avec un corps vide.Le fichier
policyest un200au corps de longueur nulle. Il ne porte aucun indicateur, mais il doit exister : GnuPG interprète son absence comme « ce domaine n’exploite aucun Web Key Directory » et abandonne avant de demander une clé.Le paramètre
?l=<local-part>est lu et ignoré. L’honorer transformerait le point de terminaison en une consultation par adresse fournie par l’appelant plutôt que par un condensat que l’appelant devait déjà connaître.Le point de terminaison ne sert que les identités publiées propres à ce déploiement (
crypto_identity). Il ne sert jamais une clé de correspondant mise en cache (peer_key) : ce matériel n’a jamais été vérifié par nous et n’est pas à nous pour être publié sous notre propre nom. Il n’existe aucune option pour changer cela.Il n’y a délibérément ici aucune limitation de débit ni aucune mise en forme uniforme des 404. Un Web Key Directory est par construction un oracle public, le condensat ne couvre que la partie locale (il répond donc à des devinettes plutôt qu’il n’énumère), et les adresses figurent à l’extérieur de chaque message que le domaine envoie. Voir pepsi-keys(1) et le chapitre de gestion des clés du manuel.
Pour la forme avancée,
openpgpkey.<domain>doit se résoudre vers cet hôte et le certificat servi doit couvrir ce nom ; c’est un nom SNI sur le même listener HTTPS, non un second listener. pepsi-setup(1) l’ajoute à la demande certbot et signale tout nom qui ne se résout pas.- GET /addin/manifest.xml
- GET /addin/taskpane.html
Le module complémentaire Outlook : un manifeste et un volet des tâches d’une seule page qui permettent à un utilisateur d’un système de courrier situé derrière cette passerelle de demander qu’un message soit signé ou chiffré. Le volet pose
X-Pepsi-Sign/X-Pepsi-Encryptsur le message en cours de rédaction ; pepsi-stage-encrypt(1) les lit et les retire avant que le message ne parte sur le fil.Les deux routes donnent
404à moins que[pepsi-httpd] ADDINne soit défini. Deux espaces réservés sont substitués à chaque requête : l’origine publiquehttps://de cette passerelle (depuisADDIN_URL, sinon leHostde la requête ; le schéma est toujourshttps, car Outlook refuse de charger les ressources d’un module complémentaire en HTTP simple – une originehttp://configurée est refusée au démarrage, sauf si son hôte est en boucle locale, ce qui est autorisé pour les essais locaux) et un identifiant de manifeste dérivé de cette origine — stable pour un déploiement et distinct entre déploiements, de sorte que deux passerelles chargées latéralement dans une même organisation Exchange n’entrent pas en collision. UnHostcontenant autre chose que ce qu’un nom d’hôte et un port peuvent contenir est refusé par400plutôt qu’échappé.La distribution se fait par chargement latéral : l’administrateur Exchange pointe « Applications intégrées » vers l’URL du manifeste. Voir le chapitre « Microsoft Exchange comme passerelle » du manuel.
- GET /metrics
Statistiques du pipeline au format d’exposition texte Prometheus. Les jauges en direct (
pepsi_stage_active_messages,pepsi_pause_backlog) sont lues directement depuis la file ; les compteurs (pepsi_stage_timeouts_total,pepsi_stage_crashes_total,pepsi_stage_messages_total,pepsi_stage_duration_seconds_totalet les globauxpepsi_stages_executed_total/pepsi_messages_processed_total) proviennent des tables de statistiques que pepsi-dispatch(1) met à jour.Le point de terminaison est d’administration : les profondeurs de file, les comptes de plantages et de timeouts par étape, les totaux cumulés et chaque nom d’étape choisi par l’opérateur décrivent ensemble la quantité de courrier que porte ce déploiement et la façon dont son pipeline est construit. Il n’est donc servi que sur un listener marqué
ADMIN = yes, autorisé à porter effectivement la surface d’administration, et répond partout ailleurs un simple404— celui qu’obtient tout chemin inconnu. Contrairement à/api/v1, il ne demande aucun identifiant, car un collecteur Prometheus n’en a aucun à présenter : l’indicateur de listener est tout le contrôle d’accès, ne marquez donc qu’un listener que seul le système de supervision peut atteindre.Pointez le collecteur vers le listener d’administration, ou ajoutez
ADMIN = yesau listener qu’il interroge déjà (un listener en clair doit être sur la boucle locale ou une socket UNIX — voir La surface d’administration ci-dessous). Lier pepsi-httpd lui-même à une adresse privée n’est pas une solution de rechange : le même processus doit répondre àmta-sts.<domain>(RFC 8461) et àopenpgpkey.<domain>depuis l’internet public, et il n’existe pas de liaison de listener par route. La première collecte refusée parce que son listener n’est pas marqué est journalisée avec le nom du listener.- POST /resume
Libère un message en pause dans le pipeline : la ligne correspondante passe de
pausedàpendinget le dispatcher est notifié afin que l’étape responsable s’exécute à nouveau. C’est la cible du webhook de paiementpepsi-resumede GNU Taler (voir pepsi-setup(1) et[pepsi-payments]), appelé lorsqu’un bon de commande est payé.La requête doit porter
Authorization: Bearer <token>correspondant auRESUME_AUTHORIZATION_TOKENconfiguré (comparé en temps constant), et un corps JSON{"message_id": "<token>"}nommant le jeton externe du message (l”order_iddu marchand). Réponses :200lorsque le message existe (un message en pause a été repris, ou il n’était déjà pas en pause — l’appel est idempotent),404lorsque le jeton est inconnu,401en cas d’autorisation échouée,400sur un corps malformé,413sur un corps de plus de 4 Kio. Le point de terminaison est désactivé (404) lorsqueRESUME_AUTHORIZATION_TOKENn’est pas configuré.- /api/v1/…
L’API d’administration — la file, le résumé de santé, la configuration, le magasin de clés, le journal d’audit. Servie uniquement sur un listener marqué
ADMIN = yes, et seulement à un principal authentifié. Voir La surface d’administration ci-dessous et le chapitre « L’API d’administration » du manuel pour la référence des points de terminaison, la table des portées et la forme des erreurs.- /ui, /ui/…
La console d’administration : un frontal de navigateur rendu côté serveur la file, le magasin de clés, la configuration et les journaux. Servie sur les mêmes listeners
ADMIN = yesque/api/v1, sous la même authentification, les mêmes vérifications de portée et le même journal d’audit — elle est cliente de l’API et ne lui ajoute aucune capacité. Les pages sont/ui(tableau de bord),/ui/login,/ui/logout,/ui/queue,/ui/queue/ID,/ui/queue/ID/{requeue,bounce,cancel},/ui/identities,/ui/identities/ID,/ui/identities/ID/{publish,unpublish,vks,primary,revoke,delete},/ui/identities/request/{generate,register},/ui/peers,/ui/peers/ID/delete,/ui/ca-trust,/ui/ca-trust/ID/delete,/ui/config,/ui/config/SECTION,/ui/logs,/ui/logs/{mail,tls,dns},/ui/domains,/ui/domains/check,/ui/secure,/ui/secure/TOKEN,/ui/secure/TOKEN/ACTION,/ui/setup,/ui/setup/STEP (GET et POST),/ui/setup/tasks(GET et POST),/ui/setup/tasks/ID et l’unique feuille de style/ui/static/console.css.Attente d’une tâche de configuration.
GET /api/v1/setup/tasks/ID accepte?wait=SECONDS (60 au plus, combinable avec?since=SEQ) et répond dès que la tâche est terminée ou a une ligne de progression postérieure à SEQ, ou lorsque le temps est écoulé ; la page de tâche de la console attend de la même façon jusqu’à 20 secondes par rechargement. Ni l’une ni l’autre n’interroge la base de données en boucle : le serveur maintient une connexionLISTENsur les canauxsetup_task_doneetsetup_task_progress, que les écritures de l’applicateur notifient avec l’identifiant de la tâche, et réveille les requêtes qui attendent cette tâche. La connexion est distincte du pool de requêtes et se reconnecte d’elle-même avec un délai croissant ; tant qu’elle est coupée, une attente rend la main aussitôt plutôt que de rester bloquée, et la page de la console se replie sur un rechargement toutes les cinq secondes.L’interrupteur de télémétrie. La question
SHARE_TELEMETRYde l’interview ne peut recevoir la réponse oui que lorsque pepsi-telemetry-client(1) est en cours d’exécution (en sommeil tant que la télémétrie est désactivée) et que[pepsi] SYSTEM_IDest configuré, car ce serveur ne génère jamais d’identifiant et un oui sans démon pour y donner suite ne ferait rien. Il apprend l’un et l’autre de la ligne de vivacité du démon danspepsi.telemetry_client, qu’il peut lire mais non écrire, et qui porte un booléen pour l’identifiant, jamais sa valeur. Sinon, la case à cocher est rendue désactivée avec des indications (démarrer l’unité ; ajouter unSYSTEM_ID),GET /api/v1/setup/questionsmarque la question"disabled": trueavec undisabled_reason, et un oui passant parPUT /api/v1/setup/answersou par une tâchewrite-configest refusé avec409 telemetry_client_not_ready.GET /api/v1/setup/telemetryrapporte le même état (sharing,can_enable,reason,client). La désactivation de la télémétrie n’est jamais refusée. Lewrite-configde l’applicateur notifie le démon après avoir écrit le fichier, dans un sens comme dans l’autre.Une liste d’autorisation de chemins en reverse-proxy doit inclure aussi les familles
/ui/setup*et/ui/secure*: la première est tout le questionnaire de configuration initiale du navigateur, la seconde les pages d’administration du lien sécurisé.La console ne livre aucun JavaScript et ne charge rien depuis un autre hôte ; chaque action est un formulaire, chaque action destructrice est confirmée, et chaque formulaire modifiant porte le jeton CSRF de la session en champ caché (une requête modifiante dont l”
Originnomme un autre hôte est refusée d’emblée). Elle n’exerce aucun contrôle de service — il n’y a délibérément aucun bouton de redémarrage, d’arrêt ou de sauvegarde. Voir le chapitre « La console d’administration » du manuel.- /3.0/…, /3.1/…
L’API REST de GNU Mailman 3 – version amont 3.3.10, réimplémentée pour que les logiciels écrits pour GNU Mailman 3 (
mailmanclient, Postorius, HyperKitty) fonctionnent sans modification avec Pepsi. Servie uniquement sur un listener marquéLIST_API = yes, sous la même règle de liaison queADMIN(clair sur boucle locale, socket UNIX, ou TLS), et authentifiée par HTTP Basic contre l’unique paire[pepsi-list]API_USER/API_PASS. Ce n’est pas/api/v1: public différent, enveloppe de collection différente, forme d’erreur différente, aucune portée. Voir le chapitre « L’API REST de GNU Mailman 3 » du manuel.LIST_API_TESTING = yesarme en outreGET /3.1/reserved/reset, qui vide toutes les tables de listes et d’archives. Il existe pour les suites de tests de compatibilité et ne doit pas être posé sur un déploiement portant du courrier réel.- GET /favicon.ico
- GET /favicon-VERSION.svg
Le badge Pepsi comme icône du navigateur, servi sur chaque écouteur quels que soient ses drapeaux : une icône ne dit rien du déploiement. La console, sa page de connexion et les pages des listes de diffusion font référence au SVG, dont le nom porte le début de son SHA-256 ; comme une nouvelle icône est une nouvelle URL, il est envoyé avec
Cache-Control: public, max-age=31536000, immutable./favicon.ico(16, 32 et 48 px) est ce qu’un navigateur va chercher de lui-même pour une page qui ne référence aucune icône ; son URL ne peut pas changer, il peut donc être mis en cache une semaine. Les deux portent unETaget répondent304à unIf-None-Matchcorrespondant. Les fichiers sont compilés dans le binaire.- /lists, /lists/…, /archives/list/…, /robots.txt
L”interface web publique des listes de diffusion : l’index des listes, la page d’information d’une liste, les formulaires d’abonnement et de désabonnement, les pages de confirmation, la cible de désabonnement en un clic (RFC 8058), les archives (aux formes d’URL de HyperKitty, pour que les liens
Archived-At:d’un déploiement migré continuent de fonctionner) et leur recherche. Servie uniquement sur un listener marquéLISTS = yes, à quiconque, sans identifiant.C’est le seul drapeau dont le conseil de déploiement est l’inverse des deux autres.
ADMINest pour l’exploitant etLIST_APIporte un mot de passe partagé : tous deux refusent d’être servis là où leurs identifiants traverseraient un réseau en clair ; cette surface existe pour être atteinte depuis l’internet ouvert, cette restriction ne lui est donc délibérément pas appliquée. Un listenerLISTSen clair est le choix de l’exploitant et reçoit une ligne de journal plutôt qu’un refus – mais un formulaire d’abonnement porte l’adresse de quelqu’un : placez-le sur un listener TLS.Ces pages ne livrent aucun JavaScript, et leur Content-Security-Policy n’a par conséquent aucun
script-src./robots.txtautorise les archives et interdit la recherche, la seule route qui exécute une requête plein texte par appel ; la recherche est en outre limitée en débit sur son propre budget, car un robot qui ignore le fichier doit rester supportable.Les deux budgets s’expriment en requêtes par minute et par adresse source (un client IPv6 par
/64), lues dans la section[pepsi-list]:- WEB_RATE_LIMIT
(entier, facultatif) Toutes les routes de cette interface et du niveau membre ci-dessous. Valeur par défaut
600.- WEB_SEARCH_RATE_LIMIT
(entier, facultatif) La route de recherche, en plus de la limite ci-dessus. Valeur par défaut
30.
Une valeur inférieure à
1est relevée à1: aucune des deux ne peut être désactivée. Derrière un proxy inverse, chaque requête arrive par la socket UNIX sans adresse de client, de sorte que tous les clients partagent un même budget ; relevez-y les limites et laissez le proxy limiter par client.Le même drapeau sert aussi le palier des membres –
/lists/register,/lists/verify/<token>,/lists/sign-in,/lists/sign-out,/lists/resetet/lists/me– ainsi que la console du propriétaire et du modérateur à/lists/<list-id>/admin.... Ni l’un ni l’autre n’est un second drapeau : un système de comptes que personne ne peut atteindre n’est pas un choix de déploiement que quiconque ferait.Ces pages emploient
pepsi.list_sessionet les cookiespepsi_list_session/pepsi_list_csrf, qui ne sont pas lespepsi_session/pepsi_csrfde la console de l’exploitant et n’y accordent rien – ni l’inverse. L’autorité sur une liste est une interrogation de la liste des membres et non un drapeau de compte : un enregistrementlist_memberavecrole = owneroumoderatorpour cette liste, pluslist_user.is_server_ownercomme seule autorité globale du palier. Un appelant qui ne l’a pas obtient la page « introuvable » ordinaire, car sur une surface publique un refus qui se distingue a révélé que la chose existe. Voir la section « Deux systèmes de comptes » du manuel.
85.1.3.1.5. La surface d’administration¶
Un listener portant ADMIN = yes sert en outre /api/v1, la console /ui et /metrics. Sur tout autre listener, ces routes répondent un simple 404, octet pour octet celui qu’obtient tout chemin inconnu, de sorte que publier le listener public ne publie pas l’administration et qu’un listener public ne peut pas être sondé pour savoir si l’administration est activée sur ce déploiement.
/api/v1 et /ui authentifient et autorisent en outre chaque requête, comme ci-dessous. /metrics non : un collecteur n’a aucun identifiant à présenter, l’indicateur de listener est donc tout ce qui se dresse devant lui.
pepsi-httpd refuse de les servir sur un listener marqué qui les transporterait en clair hors de cet hôte : le TCP en clair n’est accepté que sur une adresse de boucle locale, tandis que les listeners TLS et les sockets UNIX conviennent toujours. Un listener activé par socket (SERVE = systemd) ne convient jamais sans TLS, quoi que lie son unité — y compris un ListenStream= sur la boucle locale ou sur un chemin UNIX — car l’adresse du descripteur hérité n’est pas visible d’ici, et la traiter comme autorisée laisserait passer le cas le plus strict par la vérification la plus lâche. Puisque le déploiement livré est activé par socket, l’administration demande un listener à elle (une socket UNIX, ou une socket TLS). Un listener qui échoue au test journalise un avertissement au démarrage et ne sert que les points de terminaison publics ; pepsi-setup(1) signale la même chose au moment de la configuration.
Trois mécanismes d’authentification se résolvent en un seul modèle d’identité, essayés dans cet ordre — un identifiant explicitement présenté l’emporte toujours, de sorte qu’un jeton délibérément étroit n’est jamais silencieusement élargi :
Authorization: Bearer pepsi_<id>.<secret>— une ligne depepsi.api_token. Seul le condensat de la moitié secrète est stocké.Un cookie de session issu de
POST /api/v1/auth/login— ou du formulairePOST /ui/loginpropre à la console, qui frappe la même session — avec un jeton CSRF requis sur chaque requête modifiante. Les sessions ont par défaut un délai d’inactivité de 30 minutes et une durée de vie dure de 12 heures ([pepsi-admin] SESSION_IDLEetSESSION_LIFETIME).SO_PEERCREDsur un listener à socket UNIX :root, ou un membre de[pepsi-admin] ADMIN_GROUP, est administrateur sans aucun identifiant. C’est ce qui fait fonctionner l’amorçage au premier démarrage et ce sur quoi s’appuie la configuration livrée.
PAM n’est délibérément pas pris en charge : cela placerait un chemin d’authentification privilégié et la sémantique des comptes système à l’intérieur de la couche web du serveur de courrier.
L’autorisation se fait par portée, déclarée par point de terminaison (config:read, config:write, keys:read, keys:write, peers:write, queue:read, queue:write, logs:read, setup:write, secure:read, secure:write, plus own:<address> pour un principal confiné à une seule adresse). GET /api/v1/openapi.json est engendré depuis la table de routes propre au serveur et constitue la liste faisant foi pour un build donné.
secure:read et secure:write sont séparées parce que révoquer un message à lien sécurisé en détruit l’unique copie ; une suppression irréversible n’a pas sa place derrière une capacité nommée « read ».
La génération ou la révocation de matériel de clé ne se fait jamais ici : le matériel privé appartient au seul rôle de base pepsi-crypto. Générer une clé gérée par le serveur (POST /api/v1/identities), enregistrer la propre clé publique d’un utilisateur (POST /api/v1/identities/client) et révoquer une identité (PATCH /api/v1/identities/{id} avec {"status": "revoked"} et un revocation_reason facultatif, ce qui détruit la moitié privée d’une clé de signature seule) sont au lieu de cela demandés, sous forme de lignes generate-identity, register-client-key et revoke-identity dans pepsi.setup_task que pepsi-setup apply revalide et exécute en tant que pepsi-crypto (voir ci-dessous). Un PATCH de révocation est donc asynchrone : il ne porte que status et répond avec la tâche mise en file ; l’identité se lit revoked une fois que l’applicateur a tourné. L’enregistrement n’implique aucun matériel privé, mais une clé enregistrée comme celle de l’utilisateur devient sa face publique et vérifie des signatures en son nom : un tiers web compromis ne doit donc pas pouvoir en implanter une. Produire une CSR et importer un certificat émis ne sont pas proposés via HTTP ; utilisez pepsi-keys(1).
Libre-service sur ses propres clés. Un principal confiné à une adresse (own:<address>) peut atteindre, pour cette seule adresse, les trois écritures de clé du libre-service à la manière de pEp : POST /api/v1/identities ({"address": ..., "vks": true|false}, vks facultatif et valant par défaut [pepsi-keys] VKS_PUBLISH), POST /api/v1/identities/client ({"address": ..., "key": "<armoured OpenPGP public key>"}, 64 Kio au plus) et PATCH /api/v1/identities/{id} (dont le champ vks_wanted, lorsqu’il vaut true, enregistre une demande de téléversement vers le serveur de clés pour une identité OpenPGP active et la publie via WKD ; la tâche de réessai pepsi-keys identity publish --retry effectue le téléversement ; et dont status: revoked demande une révocation). Chaque gestionnaire revérifie l’adresse ; l’identité d’une autre adresse répond 404. Les deux routes POST et un PATCH de révocation répondent avec la tâche mise en file ({"task": ..., "kind": ..., "applier_notified": ...}), non avec la clé, et le principal qui l’a demandée peut suivre cette tâche : GET /api/v1/setup/tasks/ID répond à tout principal pour une tâche qu’il a lui-même mise en file (404 pour celle de quelqu’un d’autre, comme pour une tâche inexistante ; la propriété est le requester_key de la tâche – le sélecteur du jeton, le numéro de ligne du compte ou le login du pair, aucun d’eux n’étant jamais réutilisé –, non le nom d’affichage requested_by), et GET /api/v1/setup/tasks ne liste que ces tâches-là à un principal dépourvu de setup:write. Les formulaires de clé de la console répondent avec la page de la tâche, /ui/setup/tasks/ID, aux mêmes conditions. DELETE /api/v1/identities/{id} reste réservé à l’opérateur (keys:write) : un utilisateur retire une clé, il n’efface pas sa ligne. La console offre la même chose via les formulaires Générer une clé gérée par le serveur et Enregistrer ma propre clé de la page des identités et les actions Téléverser vers le serveur de clés et Révoquer de la page d’une identité.
L’applicateur admet ces trois types de tâches avec sa propre barrière d’autorisation : le principal demandeur doit avoir détenu keys:write, ou own:<address> pour exactement l’adresse nommée dans les paramètres de la tâche (setup:write seul ne suffit pas). Il exige ensuite que l’adresse soit dans un domaine de [pepsi-ingress] ACCEPTED_DOMAINS et, pour un enregistrement, un unique certificat OpenPGP dont les User IDs nomment l’adresse ; pour une révocation, l’identité doit appartenir à cette adresse. Sans l’applicateur, les trois répondent 503 setup_applier_unavailable, comme tout autre chemin de l’applicateur ; la mise en file exige aussi la connexion [pepsi-admin] CONFIG_DB (503 setup_write_unavailable sinon).
Le second facteur. Chacun des trois corps de requête (et les formulaires de la console) accepte un "otp": "123456" facultatif, le code de second facteur actuel du propriétaire de l’adresse (voir pepsi-keys(1), otp). Ce serveur ne peut pas le vérifier – il ne détient aucun droit sur pepsi.otp_key –, il se contente donc de transmettre le code dans les paramètres de la tâche, où l’applicateur le vérifie, avant d’agir, pour une tâche admise au seul titre de own:<address> ; un code absent, erroné, rejoué ou verrouillé fait échouer la tâche avec le motif. Un principal détenant keys:write n’est pas sollicité. DELETE /api/v1/otp/ADDRESS (keys:write, jamais accessible sous own:) met en file une tâche reset-otp qui supprime le second facteur de l’adresse, ce qui est la façon de déverrouiller un second facteur verrouillé. Les changements d’indicateurs synchrones de PATCH /api/v1/identities/{id} (is_primary, published, vks_wanted) sont effectués ici et ne sont donc pas soumis au second facteur.
Deux opérations que ce serveur refuse délibérément : écrire la surcouche de configuration (503 à moins que [pepsi-admin] CONFIG_DB ne nomme une connexion s’authentifiant comme le rôle pepsi-config, ce que le serveur vérifie au démarrage), et toute requête de configuration privilégiée lorsque le paquet de l’applicateur n’est pas installé (503, voir L’applicateur privilégié, et s’en passer ci-dessous).
La politique de rétention du journal d’audit et, lorsqu’il est activé, du journal de courrier est appliquée par la commande prune de ce binaire, non par le serveur : chaque compte de traitement du courrier peut ajouter à ces tables sans pouvoir en effacer, et le rôle pepsi-httpd est le seul rôle de service qui y détient DELETE (l’unique autre détenteur est le rôle pepsi-config de l’opérateur). Le pepsi-log-prune.timer livré l’exécute quotidiennement sous ce compte, de sorte que la rétention s’applique que le serveur web tourne ou non — c’était auparavant une tâche de fond de serve, démarrée seulement lorsqu’un listener servait la surface d’administration, et un déploiement sans la console conservait chaque enregistrement indéfiniment.
Note
Un seul binaire sert la surface publique et la surface d’administration. Un bogue dans le processus serveur partagé est donc un bogue dans les deux ; l’isolation est à configurer par l’opérateur — liez le listener d’administration à une socket UNIX ou à la boucle locale.
- GET /secure/TOKEN
La page d’entrée du portail de repli par lien sécurisé. Sans session, elle demande le PIN ; avec un cookie de session valide, elle rend le message. Répond
404lorsque le jeton est inconnu (ou que le portail n’est pas configuré),410lorsque le message a expiré, et429tant que le jeton est verrouillé. Voir le chapitre sur le lien sécurisé du manuel et pepsi-stage-secure-link(1).- POST /secure/TOKEN
Vérifie le PIN. En cas de succès, elle pose le cookie de session et redirige (
303) vers la page ci-dessus ; sur un mauvais PIN, elle réaffiche le formulaire avec401, et sur la tentative qui épuiseMAX_ATTEMPTS, elle répond429et verrouille le jeton pour une durée qui double à chaque nouveau verrouillage. Une soumission sans PIN reçoit également401, mais n’est pas comptée dansMAX_ATTEMPTS: ce n’est pas une devinette, et un client qui resoumet le formulaire vide dépenserait sinon les tentatives d’un destinataire légitime à sa place. Le PIN est soumis dans le corps de la requête et n’apparaît jamais dans une URL.- GET /secure/TOKEN/part/N
Télécharge la pièce jointe N du message. Autorisé par la session, jamais par le PIN. Toujours servi en
Content-Type: application/octet-streamavecContent-Disposition: attachmentet uneContent-Security-Policyde bac à sable, quel que soit le type de média que le message prétendait — une partietext/htmlrendue en ligne depuis l’origine propre du portail est précisément ce que la conception refuse.- POST /secure/TOKEN/reply
Compose une réponse, en
multipart/form-data(un champtextet desfilesfacultatifs). Autorisé par la session. Le destinataire et l’expéditeur d’enveloppe de la réponse sont pris dans la ligne stockée et ne sont pas des entrées ; le corps est toujours entext/plain; les téléversements sont plafonnés parMAX_REPLY_SIZE/MAX_REPLY_FILESpendant que le corps s’écoule ; et le message injecté ne porte délibérément passtate.local_origin, de sorte qu’il ne puisse hériter des privilèges de soumission. Répond404lorsqueREPLY_STAGEn’est pas défini (répondre relève d’un consentement explicite, par déploiement).Chaque réponse du portail porte, par route plutôt que par hôte,
Content-Security-Policy: default-src 'none'(avec un nonce par réponse pour l’unique feuille de style en ligne et aucune source de script),Referrer-Policy: no-referrer,X-Content-Type-Options: nosniff,X-Frame-Options: DENYetCache-Control: no-store. Le cookie de session est limité àPath=/secure/,HttpOnly,SecureetSameSite=Strict, de sorte que partager une origine avec les points de terminaison ci-dessus est sûr par construction et non par discipline de déploiement.
85.1.3.1.6. L’applicateur privilégié, et s’en passer¶
pepsi-httpd n’effectue jamais de changement privilégié. Il consigne une ligne d’intention dans pepsi.setup_task et se connecte à une socket de sonnette ; l’activation par socket de systemd transforme cette connexion en un pepsi-setup apply root de courte durée, qui valide la ligne et fait le travail. Le modèle de confiance complet est dans pepsi-setup(1), Le modèle de confiance de l’applicateur.
Cela rend la capacité retirable, et Debian l’empaquette séparément :
pepsiLe serveur de courrier proprement dit : les binaires — y compris pepsi-setup lui-même —, le schéma, les modèles et toutes les autres unités (mis à part
pepsi-stage-detect-languageetpepsi-telemetry, empaquetés séparément). Un déploiement administré depuis un terminal n’a besoin de rien d’autre.pepsi-httpd-adminpepsi-setup-apply.socketetpepsi-setup-apply.service, et rien d’autre. C’est ce qui transforme une requête HTTP en un changement sous/etc/pepsi.pepsile Recommends, de sorte qu’une installation par défaut l’a ;apt remove pepsi-httpd-adminréussit sans toucher àpepsi.
Avec ce paquet retiré, rien ne vide pepsi.setup_task. La boucle est rompue au mécanisme, non au bouton : une insertion dans cette table devient inerte, quoi qu’il advienne de la couche web. (Une installation depuis les sources obtient le même levier avec make install INSTALL_ADMIN_UNITS=no.)
Inerte, et cela le reste : remettre le paquet n’agit pas sur ce qui s’est accumulé pendant son absence. Son postinst exécute pepsi-setup apply --clear, qui supprime chaque ligne en file sans en exécuter une seule, de sorte qu’armer l’applicateur ne soit jamais aussi l’événement qui exécute une demande dont personne ne se souvient. Voir pepsi-setup(1).
85.1.3.1.6.1. Ce que fait alors la console¶
En lecture seule, et elle le dit. Chaque réglage sur lequel porte le questionnaire de configuration initiale est toujours affiché, avec sa réponse en attente ou sa valeur par défaut ; chaque contrôle qui en modifierait un est désactivé, sous une bannière nommant le paquet manquant. Les pages ne sont pas masquées : un opérateur qui a délibérément retiré l’applicateur doit encore pouvoir voir ce que le déploiement est configuré pour faire.
L’API refuse les mutations correspondantes avec un statut et un code qui leur sont propres plutôt qu’un échec générique
HTTP/1.1 503 Service Unavailable
{"code": "setup_applier_unavailable",
"hint": "the administrative package 'pepsi-httpd-admin' is not installed …",
"detail": {"package": "pepsi-httpd-admin", "unit": "pepsi-setup-apply.socket"}}
Cela s’applique à POST /api/v1/setup/tasks, /setup/preflight, /setup/dns-check, /setup/certificates, PUT /api/v1/setup/answers, POST /api/v1/identities, POST /api/v1/identities/client, à un PATCH /api/v1/identities/{id} de révocation et à DELETE /api/v1/otp/{address}, ainsi qu’aux propres envois de formulaires de la console — POST /ui/setup/{step}, /ui/setup/tasks, /ui/identities/request/{generate,register}, /ui/identities/{id}/revoke et /ui/domains/check, qui est le bouton « vérifier maintenant » de la page des domaines. Chacun d’eux atteint la file par une unique fonction de mise en file qui demande d’abord, de sorte que le refus soit une seule décision plutôt qu’une par surface ; le contrôle désactivé sur la page est une présentation par-dessus cette vérification, non un substitut. La mise en attente des réponses est délibérément incluse : le seul but d’une réponse brouillon est d’être appliquée, et un assistant qui en accumule six étapes sur un hôte où rien ne peut les appliquer est une inaction silencieuse. DELETE /api/v1/setup/answers ne passe pas par cette barrière — écarter des brouillons ne peut causer aucun changement privilégié, et une console en lecture seule doit encore pouvoir vider un questionnaire à moitié terminé.
Notez le code distinct. setup_write_unavailable (pas de connexion CONFIG_DB) et setup_applier_unavailable appellent des remèdes différents, et l’applicateur est signalé en premier lorsque les deux s’appliquent : configurer un rôle de base n’aurait pas aidé.
Ce qui n’est pas affecté, c’est /api/v1/config et les pages de configuration de la console. Celles-ci écrivent pepsi.config_override, la surcouche en base que chaque composant lit à l’exécution — non un fichier de /etc. Leur seule barrière est [pepsi-admin] CONFIG_DB, car l’applicateur n’a rien à y voir et étendre la règle de lecture seule pour les couvrir retirerait une capacité dont la scission du paquet ne traitait pas. Les deux couches sont décrites sous Superposition de la configuration dans le manuel.
85.1.3.1.6.2. Comment il le détecte¶
En regardant deux fichiers, qui à eux deux répondent installé et armé :
pepsi-setup-apply.socketexiste comme fichier d’unité dans un répertoire que systemd parcourt (/etc/systemd/system,/run/systemd/system,/usr/local/lib/systemd/system,/usr/lib/systemd/system,/lib/systemd/system) ; et[pepsi-admin] APPLY_SOCKETexiste, est une socket, et est accessible en écriture à ce compte — se connecter à une socket UNIX exige la permission d’écriture.
Deux stat et un access, sans privilège — ce qui compte, car le serveur est déjà descendu vers le compte non privilégié pepsi-httpd et ne peut rien demander à dpkg ni à systemd. Il ne se connecte délibérément pas : se connecter est la sonnette, et démarrerait un processus root à chaque rendu de page.
Aucun des deux tests n’est redondant. Sans le premier, un nœud de socket périmé se lit comme une sonnette vivante — et les nœuds périmés arrivent : le RemoveOnStop= de systemd vaut « off » par défaut (l’unité livrée le règle ; une unité éditée à la main peut ne pas le faire), et un paquet dont les fichiers sont supprimés plutôt qu’arrêtés laisse purement et simplement le nœud derrière lui. C’est exactement le cas pour lequel cette fonctionnalité existe, et s’y tromper échouerait de façon ouverte. Sans le second, un paquet fraîchement installé dont la socket n’a jamais été activée se lirait comme prêt, et chaque tâche resterait pending à jamais.
La vérification échoue de façon fermée. Un chemin qui ne peut pas être examiné, un chemin qui n’est pas une socket, une erreur de permission — chaque incertitude se lit comme « indisponible » et la console passe en lecture seule. L’échec récupérable est de se voir dire d’installer quelque chose qui est déjà installé ; l’irrécupérable est de croire qu’un changement a été fait.
Deux conséquences :
Un site qui vide la file autrement —
pepsi-setup apply --oncedepuis cron, une unité écrite à la main, un hôte sans systemd, des fichiers d’unité quelque part où systemd ne cherche pas — n’a pas de sonnette (ou pas de fichier d’unité) et se lit comme indisponible.[pepsi-admin] APPLIER = yesoutrepasse la sonde. C’est une promesse que fait l’opérateur ; rien ici ne peut la vérifier.L’inverse : la socket peut être présente alors que l’unité de service est masquée ou cassée, auquel cas une tâche est mise en file et reste
pending. La page des tâches montre ce statut plutôt que de prétendre autre chose. Les deux unités sont livrées dans un seul paquet : c’est donc un état fabriqué à la main plutôt qu’un résultat d’empaquetage.
[pepsi-admin] APPLIER = no rend la console en lecture seule par la seule configuration, pour un déploiement qui ne peut pas modifier son jeu de paquets.
85.1.3.1.7. Configuration¶
Les éléments suivants sont documentés dans pepsi.conf(5) : les options de la section [pepsi-httpd] (MAX_CONNECTIONS, MAX_CONNECTIONS_PER_IP, DB_POOL_SIZE, la paire ADDIN / ADDIN_URL qui active le module Outlook, et le RESUME_AUTHORIZATION_TOKEN facultatif qui protège POST /resume), les sections de socket et de certificat SNI [pepsi-httpd-listener-<name>] et [pepsi-httpd-cert-<name>] (y compris les drapeaux de listener ADMIN, LIST_API, LIST_API_TESTING et LISTS), la section [pepsi-admin] qui configure l’API d’administration, la section [pepsi-list] qui contient l’identifiant API_USER/API_PASS de l’API Mailman et API_RATE_LIMIT ainsi que les WEB_RATE_LIMIT/WEB_SEARCH_RATE_LIMIT de l’interface web, la section [pepsi-autoconfig] à partir de laquelle le document d’autoconfiguration est bâti, et la section [pepsi-secure-link] qui active le portail.
85.1.3.1.8. Abandon de privilèges¶
Pour lier les ports privilégiés (80/443), pepsi-httpd est souvent démarré en tant que root. Il ne sert pas en tant que root : une fois chaque listener configuré lié (et tout matériel de clé TLS lu), le processus abandonne ses privilèges au profit du compte de service non privilégié pepsi-httpd, abandonnant tous les groupes supplémentaires et basculant vers le gid et l’uid de ce compte avant d’accepter la moindre connexion. Le compte doit donc exister avant que le serveur ne soit démarré en tant que root ; créez-le (par exemple useradd --system --no-create-home --shell /usr/sbin/nologin pepsi-httpd) dans le cadre de l’installation.
Si l’abandon ne peut être complété en s’exécutant en tant que root — le plus souvent parce que le compte pepsi-httpd n’existe pas — le serveur journalise une erreur et quitte sans servir, plutôt que de risquer de s’exécuter en tant que root. Lorsqu’il est démarré par un utilisateur non root, aucun changement de privilège n’est effectué (le serveur s’exécute simplement en tant que l’utilisateur invoquant) ; il ne s’exécutera jamais en tant que root.
85.1.3.1.9. Matériel de clés TLS¶
Le certificat et la clé privée de chaque listener TLS — ses propres TLS_CERT/TLS_KEY et chaque certificat SNI [pepsi-httpd-cert-*] — sont lus une seule fois, au démarrage, avant l’abandon de privilèges décrit ci-dessus. L’endroit d’où ils sont lus dépend de la façon dont le serveur a été démarré.
Sous systemd (le déploiement empaqueté). L’unité s’exécute dès le départ sous l’utilisateur non privilégié pepsi-httpd — elle ne détient jamais root — et ne peut donc pas ouvrir /etc/letsencrypt/{live,archive}, réservé à root par certbot. Elle n’en a pas besoin : pepsi-setup(1) écrit un drop-in /etc/systemd/system/pepsi-httpd.service.d/10-tls-credentials.conf comportant une entrée LoadCredential= par fichier. systemd ouvre 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 et invisible pour toute autre unité — où pepsi-httpd recherche chaque chemin configuré avant de se rabattre sur la lecture du chemin lui-même. La configuration n’en est pas affectée : TLS_CERT/TLS_KEY désignent toujours les fichiers réels, et c’est de ces chemins que sont dérivés les noms de credentials.
Deux conséquences :
Relancez
pepsi-setup runaprès avoir ajouté, déplacé ou supprimé un certificat. Le drop-in est régénéré à partir de la configuration et n’énumère délibérément que des fichiers qui existent, car systemd refuse de démarrer une unité dont la source de credential est absente.Un credential est matérialisé au démarrage de l’unité ; un certificat renouvelé n’atteint donc les clients qu’après un redémarrage. Le hook de déploiement certbot installé par pepsi-setup s’en charge (
systemctl try-restart).
Démarré directement, sans systemd. Le serveur est démarré en tant que root, lit les fichiers lui-même et n’abandonne ses privilèges qu’ensuite : aucun credential n’intervient et les permissions des fichiers n’ont jamais d’importance.
Un certificat qui ne peut pas être chargé est signalé et ignoré, sans être fatal : les noms d’hôte SNI qu’il aurait servis cessent d’être disponibles en HTTPS, tandis que tous les autres certificats continuent de fonctionner. Seul un listener TLS qui ne dispose plus d’aucun certificat utilisable fait sortir le serveur. Un unique fichier illisible ne peut donc pas entraîner avec lui la politique MTA-STS de domaines sans rapport — ni le point de terminaison des métriques.
85.1.3.1.10. Commandes¶
- serve
Exécute le serveur jusqu’à interruption. Nécessite que le schéma ait été installé avec pepsi-setup(1).
- prune
Supprime les enregistrements d’audit plus anciens que
[pepsi-admin] EVENT_RETENTION_DAYS, les enregistrements du journal de courrier plus anciens queMAIL_LOG_RETENTION_DAYSet les sessions d’administration expirées, puis se termine. Exécuté en root, il descend d’abord au comptepepsi-httpd, dont le rôle de base de données détient le droitDELETE.pepsi-log-prune.timerl’exécute quotidiennement ; sur un système sans systemd, exécutez-le depuis cron.
85.1.3.1.11. Options globales¶
- -c FILE, –config FILE
Lit la configuration depuis FILE au lieu de parcourir les emplacements par défaut.
- -L LOGLEVEL, –log LOGLEVEL
Règle la verbosité de journalisation (par défaut
info).- -v, –verbose
Affiche les messages de journal de toutes les sources.
- -h, –help ; -V, –version
Affiche un résumé d’utilisation / la version et quitte.
85.1.3.1.12. Signaux¶
- SIGINT
Amorce l’arrêt et quitte proprement.
- SIGTERM
Non capturé : la disposition par défaut met fin au processus sur-le-champ, coupant toute requête en vol. Rien n’est perdu de ce fait — la file d’attente est dans PostgreSQL et ce serveur ne conserve aucun état propre — et c’est ainsi que
systemctl stopmet fin à l’unité.
85.1.3.1.13. Code de sortie¶
- 0
Arrêt propre.
- 1
Une erreur s’est produite (par exemple un fichier de configuration malformé, un certificat illisible, ou une connexion de base de données échouée). La raison est écrite dans le journal.
85.1.3.1.14. Exemples¶
Servir via HTTPS
pepsi-httpd -c /etc/pepsi/pepsi.conf serve
Récupérer une politique pour test (en résolvant l’hôte SNI vers le serveur)
curl --resolve mta-sts.example.org:443:203.0.113.7 \
https://mta-sts.example.org/.well-known/mta-sts.txt
Vérifier que la clé d’un utilisateur est publiée, comme le ferait le client d’un correspondant
gpg --locate-external-key alice@example.org
85.1.3.1.15. Voir aussi¶
pepsi-config(1), pepsi.conf(5), pepsi-dispatch(1), pepsi-keys(1), pepsi-setup(1)
85.1.3.1.16. Bogues¶
Signalez les bogues au gestionnaire de tickets de Pepsi.