19. L’API REST de GNU Mailman 3¶
pepsi-httpd sert l’API REST de GNU Mailman 3 sous /3.0/ et /3.1/. Ce n’est pas une API de la conception de Pepsi : c’est l’interface de quelqu’un d’autre, réimplémentée pour que les logiciels écrits pour GNU Mailman 3 fonctionnent sans modification avec Pepsi. mailmanclient, Postorius et HyperKitty sont les clients pour lesquels elle existe.
La version implémentée est la 3.3.10. Cette précision compte : « Mailman 3 » n’est pas un contrat, car l’ensemble des attributs dérive d’une version corrective à l’autre alors que le chemin /3.1/ ne bouge pas. L’interface est documentée par le projet GNU Mailman à l’adresse https://docs.mailman3.org/ et c’est cette documentation – non ce chapitre – qui est la spécification. Ce qui suit est ce dont un exploitant a besoin pour la faire tourner, plus les trois endroits où Pepsi doit répondre quelque chose de vrai au lieu de copier quelque chose qu’il n’a pas.
Note
Attribution. L’arborescence des ressources, les noms d’attributs, les valeurs d’énumération, les conventions de protocole et les messages d’erreur sont ceux de GNU Mailman, dont la Free Software Foundation détient les droits, et sont reproduits ici afin d’être compatible avec eux. Voir vendor/PEPSI-VENDORING.md. Pepsi n’est pas GNU Mailman et n’est pas approuvé par le projet GNU Mailman.
Ce chapitre traite de l”interface. Ce que les ressources signifient – une liste, un membre, une action de modération, un résumé – relève de Listes de diffusion, dont la section Comment le sous-système est agencé montre où cette API se situe par rapport au /api/v1 de l’exploitant et aux pages publiques des membres ; les ressources propres aux archives sont dans Archives. Activer le listener et les identifiants qu’il demande relèvent de Installation ; les consoles de navigateur, qui sont des clients du même modèle, de La console d’administration ; et la liste de ce que Pepsi implémente, de Fonctionnalités prises en charge.
19.1. Il y a deux API dans cet arbre¶
Un exploitant rencontrera les deux, et elles n’ont rien en commun sinon le serveur :
/api/v1L’API d’administration propre à Pepsi (L’API d’administration). Son public est l’exploitant : la file, le magasin de clés, les journaux, la configuration. Elle a des comptes, des portées, des sessions, des jetons porteurs et une protection CSRF ; ses collections sont
{items, total, limit, offset}et ses erreurs{code, hint, detail}./3.0/et/3.1/Celle-ci. Son public est le logiciel de listes de diffusion. Elle a un unique mot de passe partagé et aucune portée ; ses collections sont
{start, total_size, entries}et ses erreurs sont celles de Falcon :{title, description}.
Elles ne sont délibérément pas unifiées. Tout l’intérêt de celle-ci est que ses formes ne nous appartiennent pas et ne sont donc pas à améliorer.
19.2. Où elle est servie¶
Uniquement sur un listener marqué LIST_API = yes. Sur tout autre listener, les routes /3.0/ et /3.1/ répondent un simple 404, octet pour octet ce que reçoit n’importe quel chemin inconnu, de sorte qu’un listener public ne peut pas être sondé pour savoir si l’API est activée sur ce déploiement.
La règle est la même que pour l’API d’administration, et pour la même raison – l’identifiant est un unique mot de passe statique, il ne doit donc pas traverser un réseau en clair :
un listener TLS est toujours autorisé ;
un listener sur socket UNIX est toujours autorisé (il ne quitte jamais la machine) ;
un listener TCP en clair n’est autorisé que sur une adresse de boucle locale ;
un socket activé par systemd en clair est refusé, car le serveur ne peut pas voir où systemd l’a lié.
Un listener qui demande le drapeau mais est lié ailleurs ne sert pas l’API, et pepsi-httpd le dit haut et fort au démarrage au lieu d’échouer en silence.
[pepsi-httpd-listener-mailman]
# What every existing mailman.cfg-shaped client already points at.
BIND_TO = 127.0.0.1:8001
MODE = plain
LIST_API = yes
[pepsi-list]
API_USER = restadmin
API_PASS = a-long-random-string
Avertissement
API_USER et API_PASS sont un identifiant unique partagé pour tout le serveur. Quiconque le détient peut lire et modifier chaque liste, chaque abonnement et chaque enregistrement d’utilisateur. C’est le modèle d’amont, non une simplification : gardez le listener sur la boucle locale ou derrière TLS, et ne réutilisez ce mot de passe nulle part ailleurs. pepsi-httpd refuse de démarrer lorsqu’un listener demande l’API et que l’identifiant est incomplet : une API dont tout le modèle d’autorisation est ce mot de passe, servie sans mot de passe, n’est pas un mode dégradé.
19.3. Authentification¶
HTTP Basic, contre cette unique paire, comparée en temps constant. Il n’y a ni sessions, ni jetons CSRF, ni comptes par utilisateur, ni émission de jetons : un client envoie Authorization: Basic ... à chaque requête. Un échec est un 401 avec
WWW-Authenticate: Basic realm="mailman3-rest",charset="utf-8"
et cette chaîne de domaine (« realm ») est celle d’amont, parce qu’un client peut l’afficher.
Pepsi ajoute un limiteur de débit, dont amont n’a aucun. C’est un sur-ensemble et cela ne peut pas casser un client correct, mais une suite de compatibilité tire des centaines de requêtes en quelques secondes : la valeur par défaut est donc délibérément haute – 3000 requêtes par minute et par adresse source, réglable avec [pepsi-list] API_RATE_LIMIT. Un 429 venant de cette API est celui de Pepsi, pas de Mailman.
19.4. Les deux versions diffèrent sur quatre points¶
Pas un seul, ce qui est l’erreur habituelle :
les six attributs de modèle
*_urisont des attributs de liste dans/3.0/et absents dans/3.1/, qui les atteint par/urisà la place ;/templates/...n’existe que dans/3.0/;/uris/...,/lists/<id>/uris,/domains/<host>/uriset/pluginsn’existent que dans/3.1/. Chacun est un404dans l’autre version ;les identifiants sont sérialisés différemment.
/3.0/émet un UUID sous la formeuuid.int, un entier décimal pouvant atteindre trente-neuf chiffres ;/3.1/émetuuid.hex, une chaîne. Cela touchemember_id,user_idet chaqueself_linkconstruit à partir d’eux, et c’est la raison même de l’existence de/3.1/– la forme entière n’est pas représentable dans tous les environnements JavaScript ;chaque
self_linket chaqueLocationporte son propre préfixe de version.
19.5. URI de modèles¶
Une redéfinition de modèle écrite par l’une ou l’autre version — un attribut *_uri dans /3.0/, ou /uris dans /3.1/ — est une URI que le serveur récupère, de sorte que quiconque détient l’identifiant REST choisit ce que le serveur lit. Pepsi accepte mailman: (le texte intégré) et http(s):, récupérées dans les limites de [pepsi-list] TEMPLATE_FETCH_*, et répond 400 à une URI file:, qu’amont lirait sur son propre disque. Une ligne file: qui se trouve malgré tout dans la table (écrite à la main) n’est jamais lue : l’avis est rendu comme si la ligne était absente.
19.6. Trois équivalents honnêtes¶
Trois ressources décrivent des choses que GNU Mailman a et que Pepsi n’a pas. Aucune n’est simulée.
/queuesLes files d’amont sont des répertoires de fichiers
.pck. Pepsi a une table : un message en cours est un enregistrement depepsi.workqueueà une certaine étape. Cette ressource répond donc avec les comptes réels –incorrespond à tous les messages en cours,retryà ceux mis en pause pour une tentative ultérieure,badetshuntaux deux états terminaux que le répartiteur ne remet jamais en file – leworkqueue_idtenant lieu defilebaseet le nom de la table figurant là où il y aurait un répertoire.POST /queues/<name>injecte un message à l’étape d’envoi de la liste etDELETE /queues/<name>/<id>retire un enregistrement. pepsi-queue(1) reste l’outil véritable./pluginsToujours une collection vide. Pepsi n’a ni interface de greffons ni archiveurs enfichables.
/3.0/n’a pas cette ressource./reservedLe crochet de test d’amont, qu’amont lui-même signale comme ne faisant pas partie de l’API stable.
GET /reserved/resetvide toutes les tables de listes et d’archives : il n’existe donc que sur un listener qui l’a explicitement demandé parLIST_API_TESTING = yes; partout ailleurs, c’est un404indiscernable d’un chemin inconnu, et un test vérifie ce comportement par défaut. Il est là parce que les suites de compatibilité réinitialisent l’état entre classes de test dans leur propre processus, ce qu’un serveur hors processus ne peut pas offrir.DELETE /reserved/uids/orphansest une opération vide réussie : les identifiants sont ici des UUID de version 4 et il n’y a aucune table d’orphelins à purger.
19.7. Ce qu’il faut configurer au-delà de l’identifiant¶
[pepsi-list] RELEASE_STAGELa section
[stage-<name>]où tournepepsi-stage-list-post. Un modérateur qui accepte un message retenu, commePOST /queues/<name>, y remet un message dans le pipeline, et seul le déploiement sait comment cette section s’appelle. Sans elle, ces deux opérations répondent400et le disent – ce qui vaut mieux que répondre204en perdant le message, car un modérateur qui accepte un message et le voit ne jamais arriver ne peut pas distinguer cela d’un échec de remise.[pepsi-list] DEFAULT_LANGUAGELe bas de la chaîne de préférences, rapporté par
GET /<api>/system/preferences. Vautenpar défaut.
19.8. Un exemple travaillé¶
Gardez l’identifiant hors de la ligne de commande – sur une machine partagée, il figure dans chaque liste de processus et dans l’historique du shell – en le mettant dans un fichier et en laissant curl le lire :
$ cat > ~/.mailman-netrc <<'EOF'
machine localhost login restadmin password a-long-random-string
EOF
$ chmod 600 ~/.mailman-netrc
$ alias mmcurl='curl -sS --netrc-file ~/.mailman-netrc'
$ mmcurl http://localhost:8001/3.1/system/versions
{"mailman_version":"GNU Mailman 3.3.10 (Tom Sawyer; implemented by Pepsi …)",
"python_version":"…","api_version":"3.1",
"self_link":"http://localhost:8001/3.1/system/versions",
"http_etag":"\"…\""}
$ mmcurl -X POST --data 'mail_host=lists.example.com' \
http://localhost:8001/3.1/domains
$ mmcurl -X POST --data 'fqdn_listname=devel@lists.example.com' \
http://localhost:8001/3.1/lists
$ mmcurl 'http://localhost:8001/3.1/lists?count=10&page=1'
mailman_version mérite un second regard. Il doit commencer par la chaîne sur laquelle un client fait sa comparaison, et il doit être vrai ; il nomme donc d’abord la version de l”interface et dit ce qui tourne réellement dans la parenthèse qu’amont remplit d’un nom de code. Pepsi ne prétend pas être GNU Mailman.
POST /lists/<id>/digest : bump fait avancer le volume, et send comme periodic passent par le même expéditeur que celui de la ligne de commande, de sorte qu’une livraison déclenchée depuis un client et une livraison déclenchée par une minuterie suivent le même chemin de code et portent la même numérotation.
19.9. Ce qui n’est pas implémenté¶
Un message retenu ou une demande rejetés sont abandonnés sans l’avis de rejet qu’amont envoie. L’acceptation, le rejet sans avis et le report sont complets.
19.10. La tester¶
tests/22-list-api-test.sh exécute les suites de tests d’amont elles-mêmes contre un Pepsi déployé : mailmanclient 3.3.5 (344 instructions de doctest et 36 fonctions de test) et Postorius 1.3.13 (30 fichiers, 248 fonctions de test), chacune avec seulement son dispositif de démarrage du serveur remplacé. Les remplacements résident dans contrib/compat/ et sont versionnés à côté des dépendances épinglées, de sorte que « des tests d’amont non modifiés » reste littéralement vrai de tout sauf du dispositif. Voir Suite de tests.