21. Die Verwaltungskonsole¶
Pepsi liefert eine kleine Browser-Konsole aus, die pepsi-httpd unter /ui bedient. Sie existiert für die Dinge, die auf der Kommandozeile mühsam sind: die Warteschlange beobachten, Schlüssel und Korrespondenten durchsehen, das Prüfprotokoll lesen und die Konfiguration mit sichtbarer Validierung und Herkunft ändern.
Sie ist ein Client der dokumentierten administrativen API (Die administrative API) und nichts weiter. Jede Seite ruft dieselben Bibliotheksfunktionen auf, die die /api/v1-Endpunkte aufrufen, über dieselbe Authentifizierung, dieselben Geltungsbereichsprüfungen und dasselbe Prüfprotokoll, sodass alles, was die Konsole kann, auch mit curl getan werden kann — und die beiden können sich nicht darüber uneins sein, was die Warteschlange enthält oder ob ein Konfigurationswert gültig ist.
Bemerkung
In diesem Server gibt es zwei HTTP-APIs, und sie sind nicht dasselbe. /api/v1 ist Pepsis eigene (Die administrative API), von der diese Konsole ein Client ist: Konten, Scopes, Sitzungen und CSRF, über ADMIN = yes freigeschaltet. /3.0/ und /3.1/ sind die REST-API von GNU Mailman 3 (Die REST-API von GNU Mailman 3), nachgebaut, damit Mailman-Software gegen Pepsi läuft: ein gemeinsames Passwort, keine Scopes, die Formen eines anderen, über LIST_API = yes freigeschaltet. Ein Betreiber wird beiden begegnen; sie teilen den Server und nichts weiter, und keine ist eine Version der anderen.
21.1. Drei Listener-Flags, und bei zwei davon lautet der Rat entgegengesetzt¶
pepsi-httpd bietet vier Dinge an und schaltet drei davon frei, jedes über sein eigenes Flag in [pepsi-httpd-listener-*]. Diese zu verwechseln ist der mit Abstand einfachste Weg, Pepsis HTTP-Oberfläche falsch auszuliefern, daher stehen sie in einer Tabelle:
Flag |
Was es anbietet |
Für wen es ist |
Wohin es gehört |
|---|---|---|---|
|
|
Den Betreiber. Bearer-Token, Passwortsitzungen, |
Hinter einem Tunnel. Ein Unix-Socket oder Loopback mit einem Reverse-Proxy. |
|
|
Mailinglisten-Software ( |
Loopback oder TLS. Dieselbe Weigerung wie bei |
|
|
Die anonyme Öffentlichkeit. Überhaupt keine Zugangsdaten. |
Im offenen Internet. Dies ist die eine Oberfläche, die erreichbar sein soll, daher wird die Klartextbeschränkung absichtlich nicht angewandt – aber ein Anmeldeformular trägt die Adresse eines Menschen, nehmen Sie also den TLS-Listener. |
(keines) |
MTA-STS, Web Key Directory, Mail-Autokonfiguration, das Portal für sichere Links, |
Jeden, der sie braucht, so entworfen. |
Wo auch immer der öffentliche Listener ist. |
Jede freigeschaltete Route antwortet auf einem Listener ohne ihr Flag mit einem einfachen 404 – Byte für Byte das, womit ein unbekannter Pfad antwortet. Deshalb veröffentlicht das Veröffentlichen des öffentlichen HTTPS-Listeners nicht die Verwaltung, und deshalb kann ein öffentlicher Listener nicht danach abgetastet werden, was dieser Server sonst betreibt.
21.2. Sie sicher erreichen¶
Die Konsole wird auf genau denselben Listenern eingehängt wie die API: denen mit dem Flag ADMIN = yes (siehe pepsi-httpd(1)). Auf jedem anderen Listener antworten die /ui-Pfade mit einem schlichten 404, Byte für Byte dem, was ein unbekannter Pfad antwortet, sodass den öffentlichen Listener zu veröffentlichen nicht die Konsole veröffentlicht und ein öffentlicher Listener nicht daraufhin abgetastet werden kann, ob die Verwaltung hier aktiviert ist.
pepsi-httpd weigert sich, sie auf einem gekennzeichneten Listener zu bedienen, der sie im Klartext von diesem Host wegtragen würde: Klartext-TCP wird nur auf einer Loopback-Adresse angenommen, während TLS-Listener und UNIX-Sockets stets in Frage kommen. Ein socket-aktivierter Listener (SERVE = systemd) ohne TLS kommt nie in Frage, was seine Unit auch bindet, denn der übergebene Deskriptor könnte alles sein, und ihn als zulässig zu behandeln ließe den strengsten Fall durch die lockerste Prüfung — geben Sie der Verwaltung einen eigenen Listener. Dabei geht es nicht nur um Mithören — das Sitzungs-Cookie trägt das __Host--Präfix (RFC 6265bis §4.1.3.2) nur dann, wenn es Secure gesetzt ist, also nur, wenn die Verbindung des Clients HTTPS ist, denn ein Browser weist ein __Host--Cookie zurück, das das nicht ist. Auf dem Klartext-Loopback-Bootstrap-Pfad wird keines von beidem verwendet, sodass die eigene Authentifizierung der Konsole auf einem Klartext-Origin konstruktionsbedingt schwächer ist. Ein Browser kann keinen UNIX-Socket öffnen, sodass eine typische Installation entweder
einen administrativen Listener an
127.0.0.1bindet und ihn über einen SSH-Tunnel erreicht (ssh -L 8443:127.0.0.1:8443 mail.example.com), oderdem administrativen Listener TLS und einen eigenen Namen gibt und ihn wie jede andere ins Internet gerichtete Verwaltungsoberfläche behandelt.
21.3. Anmelden¶
Zwei der drei Mechanismen der API sind aus einem Browser heraus nützlich:
Ein lokaler Operator über den administrativen UNIX-Socket wird über SO_PEERCRED identifiziert und braucht überhaupt kein Passwort. Browser können nicht mit einem UNIX-Socket sprechen, sodass dieser Weg für curl und für den Bootstrap beim ersten Lauf gedacht ist; er ist auch der Grund, warum die strenge Sitzungsrichtlinie unten erschwinglich ist.
Ein entfernter Administrator meldet sich unter /ui/login mit einem Konto aus pepsi.admin_account an (legen Sie eines mit POST /api/v1/accounts an). Sitzungen haben ein Leerlauf-Timeout von 30 Minuten und eine harte Lebensdauer von 12 Stunden unabhängig von Aktivität, und es gibt kein „angemeldet bleiben“. Diese Konsole kann einen Schlüssel widerrufen, lesen, wer mit wem korrespondiert, und die Pipeline umschreiben; ein vergessener Browser-Tab sollte das morgen nicht noch halten, und ein gestohlener Laptop sollte keine dauerhafte administrative Sitzung sein.
Bearer-Tokens funktionieren ebenfalls, aber ein Browser hat keine Möglichkeit, eines anzuhängen; sie sind für Automatisierung gegen /api/v1.
21.4. Was die Konsole kann und was nicht¶
Die Aktionen sind nur auf Nachrichten- und Schlüsselebene:
eine steckengebliebene Nachricht an ihrer aktuellen Stage wieder einreihen, sie zu einer Bounce-Stage routen oder sie ohne Zustellung löschen;
eine lokale Identität veröffentlichen oder zurückziehen, eine zur primären machen, sie löschen oder ihr Hochladen auf den Schlüsselserver anfordern;
den Applier um einen servergeführten Schlüssel bitten, darum, den eigenen öffentlichen Schlüssel eines Benutzers zu registrieren, oder darum, eine Identität zu widerrufen (alle drei über den privilegierten Applier, siehe unten);
einen zwischengespeicherten Korrespondentenschlüssel vergessen und einen Vertrauensanker entfernen. Einen Schlüssel von Hand zu importieren und eine Suchanfrage einzureihen sind keine Aktionen der Konsole: Das sind
POST /api/v1/peersundPOST /api/v1/peers/discoverauf der API oder pepsi-keys(1), worauf die Seite selbst verweist;eine Secure-Link-Nachricht widerrufen;
eine Konfigurationsüberschreibung setzen oder entfernen;
das Setup-Interview beantworten und den privilegierten Applier bitten, certbot auszuführen, das Schema zu installieren, Rollen einzurichten oder DNS zu prüfen.
Es gibt bewusst keinen Neustart-, keinen Abschalt- und keinen Sicherungsknopf. Eine Web-Konsole, die den Mailserver anhalten kann, ist eine, die dazu überlistet werden kann, den Mailserver anzuhalten, und systemd besitzt diese Aufgabe bereits. Starten Sie eine Komponente mit systemctl restart pepsi-ingress neu (die Konfigurationsseiten nennen die Unit, wenn eine Änderung eine braucht).
Alles Privilegierte hier hat diese Form: Die Konsole hält fest, was geschehen soll, und ein separates root-Programm tut es. Die Konsole kann /etc nicht schreiben, certbot nicht ausführen und keine Datenbankrolle anlegen, und darf das auch nicht können. Siehe „Setup im Browser“ unten.
Zwei weitere Dinge kann die Konsole nicht, weil der Server es nicht kann:
Schlüsselmaterial selbst erzeugen oder widerrufen. Das erfordert die Datenbankrolle
pepsi-crypto, die das Recht auf die private Spalte hält und die pepsi-httpd bewusst nicht hält — eine Web-Schicht, die Signaturschlüssel erzeugen kann, ist eine Web-Schicht, deren Kompromittierung Signaturschlüssel erzeugt. Das Formular Servergeführten Schlüssel erzeugen der Identitätsseite bittet daher nur: Es reiht einegenerate-identity-Aufgabe ein, diepepsi-setup applyerneut validiert und alspepsi-cryptoausführt. Eigenen Schlüssel registrieren (einen ASCII-armierten öffentlichen OpenPGP-Schlüssel einfügen) reiht auf dieselbe Weiseregister-client-keyein, und der Knopf Widerrufen einer Identität reiht nach Bestätigungrevoke-identityein — das Widerrufen eines reinen Signaturschlüssels vernichtet dessen privaten Teil, was ein Schreibzugriff auf diese Spalte ist. Jedes antwortet mit der eigenen Seite der eingereihten Aufgabe,/ui/setup/tasks/<id>, die der anfragende Benutzer unabhängig von seinen Geltungsbereichen öffnen darf und die das Ergebnis – erledigt, mit Grund fehlgeschlagen oder abgewiesen – in dem Moment zeigt, in dem der Applier es festhält. Alle drei antworten mit503, wenn das Applier-Paket nicht installiert ist. Jedes Formular hat außerdem ein Feld Code des zweiten Faktors: Ein Benutzer, der einen zweiten Faktor eingerichtet hat (Schlüsselverwaltung), gibt dort den aktuellen Code aus seiner Authenticator-App ein, und der Applier prüft ihn – die Konsole kann das nicht. CSRs und ausgestellte Zertifikate bleiben bei pepsi-keys(1).Die Konfigurations-Überlagerung schreiben, sofern
[pepsi-admin] CONFIG_DBnicht eine Verbindung benennt, die sich als Rollepepsi-configauthentifiziert. Ohne sie sind die Konfigurationsseiten nur lesend und sagen warum.
Die Konsole zeigt auch nie die Header oder den Nachrichtentext einer Nachricht. Die Warteschlangenseiten laden den Umschlag, die Stage, den Status und das state-JSON, und sonst nichts — strukturell, denn die Abfrage dahinter wählt diese Spalten nicht aus. Mail zu lesen ist keine administrative Funktion.
21.5. Die Seiten¶
Pfad |
Geltungsbereich |
Was sie zeigt |
|---|---|---|
|
|
Dashboard: Warteschlangentiefe nach |
|
|
Filterbare Auflistung (nach Stage und Status), paginiert, mit einer per Opt-in eingeschalteten 30-Sekunden-Aktualisierung. |
|
|
Eine Nachricht: Umschlag, Stage, Status und formatiertes |
|
|
Lokale Schlüsselpaare, filterbar nach Adresse, mit den Formularen Servergeführten Schlüssel erzeugen und Eigenen Schlüssel registrieren ( |
|
|
Eine Identität, mit ihrer Verwahrung (servergeführt oder der eigene Schlüssel des Benutzers) und ihrem Schlüsselserver-Zustand, sowie Veröffentlichen / Zurückziehen / Auf den Schlüsselserver hochladen / Zur primären machen / Widerrufen ( |
|
|
Zwischengespeicherte Korrespondentenschlüssel: Quelle, DNSSEC-Kennzeichen, Gültigkeit, Pin-Zustand. Einen zu vergessen braucht |
|
|
Die S/MIME-Vertrauensanker; das Entfernen braucht |
|
|
Jeder Abschnitt der effektiven Konfiguration für einen Geltungsbereich, samt der Angabe, wie eine Änderung wirksam wird. |
|
|
Ein Abschnitt: der Wert jeder Option, woher er stammt (Datei, global, Domain, Adresse), und die Formulare zum Setzen oder Entfernen einer Überschreibung ( |
|
|
Das Prüfprotokoll, filterbar nach Art und Akteur. |
|
|
Das optionale Protokoll pro Nachricht, filterbar nach Adresse; sagt es deutlich, wenn |
|
|
Ergebnisse ausgehender TLS-Sitzungen je Richtliniendomain. |
|
|
MX-Adressen, die der Resolver-Cache derzeit als fehlschlagend markiert. |
|
|
Die bedienten Domains, der MTA-STS-Modus, wie viele Identitäten unter jeder veröffentlicht sind, und die zu veröffentlichenden DNS-Einträge mit dem letzten Live-Urteil für jeden. Der Knopf „DNS jetzt prüfen“ braucht |
|
|
Secure-Link-Nachrichten, die das Portal hält: Korrespondenten, Ablauf, Lesevorgänge, falsche PINs, Sperren. Das Widerrufen braucht |
|
|
Eine Nachricht mit ihrer Zugriffshistorie. |
|
|
Der Einstiegspunkt des Setup-Interviews; jeder Schritt ist |
|
|
Was vom privilegierten Applier verlangt wurde, und ein Formular, um mehr zu verlangen. Ohne |
|
|
Eine Aktion mit dem gestreamten Fortschritt des Appliers, aktualisiert, sobald der Applier etwas schreibt. |
Keine Seite lädt eine unbegrenzte Ergebnismenge: Jede Auflistung läuft über eine Abfrage, die ihr eigenes LIMIT/OFFSET trägt, sodass das eine Eigenschaft des SQL ist statt dessen, was der Renderer zeichnet. Die Warteschlangen- und die Journalansichten paginieren mit ?limit=/?offset= (gekappt auf [pepsi-admin] PAGE_LIMIT, höchstens 1000). Die Schlüsselspeicher-Auflistungen — Identitäten, Korrespondenten, Vertrauensanker — holen den Umfang einer Seite plus einen Datensatz, um zu erfahren, ob es mehr gibt, und sagen klar, wenn sie gekürzt wurden; engen Sie sie mit dem Adressfilter ein oder verwenden Sie pepsi-keys(1). Die Konsole fragt nur dort nach einem COUNT(*), wo sie eine Gesamtzahl zeigt (die beiden Journalansichten); die API tut es immer, weil ihre Clients programmatisch blättern. Siehe Die administrative API.
Jede zerstörerische Aktion — eine eingereihte Nachricht löschen, eine Identität widerrufen oder löschen, einen Vertrauensanker entfernen, eine Konfigurationsüberschreibung entfernen — geht zuerst über eine ausdrückliche Bestätigungsseite. Jede Änderung wird mit derselben Ereignisart in das Prüfprotokoll geschrieben, die die API festhält, sodass /ui/logs Änderungen zeigt, die aus der Konsole, aus curl und aus den Kommandozeilenwerkzeugen gleichermaßen vorgenommen wurden.
Konfigurationswerte, die ein Geheimnis tragen könnten, werden als *** gezeigt, nie als Werte, nach derselben Regel, mit der die API maskiert. Die Liste ist bewusst großzügig: Ein fälschlich maskierter Wert kostet einen Operator einen Blick in die Datei, ein fälschlich gezeigter Wert wird an jeden veröffentlicht, der config:read hält.
21.5.1. Setup im Browser¶
/ui/setup rendert dasselbe Interview, das pepsi-setup --wizard an einem Terminal stellt — buchstäblich dasselbe, denn die Fragen, ihre Formen, ihre Standardwerte und die Bedingungen, unter denen sie gestellt werden, liegen als Daten an einer Stelle (pepsi-setup-model), und beide Front-Ends rendern sie. Eine Frage, die dem einen hinzugefügt wird, ist eine Frage, die beiden hinzugefügt wird.
Zwei getrennte Mechanismen halten es sicher:
Antworten sind Entwürfe. Jeder Schritt schreibt in pepsi.config_override mit dem draft-Flag, das jeder Konfigurationsleser strukturell herausfiltert. Nichts wird wirksam, solange das Interview läuft, sodass eine Sitzung, die auf halbem Weg abläuft, keinen Mailserver halb konfiguriert hat, und das erneute Öffnen der Seite dort weitermacht, wo es aufgehört hat. Ein Schritt mit einer schlechten Antwort rendert neu, mit der Meldung neben dem Formularfeld, und speichert nichts — nicht einmal die guten Antworten auf derselben Seite, denn ein halb gespeicherter Schritt ist einer, den ein Operator aus dem Gedächtnis rekonstruieren muss.
Aktionen sind Absichten. /ui/setup/tasks zeigt, was vom privilegierten Applier verlangt wurde, und bietet ein Formular, um mehr zu verlangen, aus seiner geschlossenen Menge: run-preflight, obtain-certificate, install-schema, provision-roles. Ein Knopf schreibt einen pepsi.setup_task-Datensatz und betätigt eine Türklingel; pepsi-setup apply, als root laufend, erledigt die Arbeit und streamt seinen Fortschritt zurück in den Datensatz, den /ui/setup/tasks/<id> anzeigt. Während die Aufgabe läuft, lädt sich die Seite selbst neu (ein <meta http-equiv="refresh">, kein Timer in einem Skript), und das Neuladen fragt nicht ständig ab: Es fordert die Seite erneut an, zusammen mit dem, was sie bereits gezeigt hat, und der Server hält diese Anfrage bis zu 20 Sekunden lang zurück, bis die Benachrichtigung des Appliers meldet, dass es eine neue Fortschrittszeile oder ein Ergebnis gibt. Das Ergebnis erscheint daher in dem Moment, in dem es existiert. Hat der Server seine Listener-Verbindung zur Datenbank verloren, antwortet er sofort, und die Seite lädt sich ersatzweise alle fünf Sekunden neu. Es gibt keine Neustart- oder Abschaltaktion; eine Änderung, die einen Komponentenneustart braucht, endet damit, dass Sie ihn ausführen.
Beide benötigen setup:write, auch die Lesezugriffe: Die vorgemerkten Antworten sind die Gestalt einer im Aufbau befindlichen Installation, und die Aufgabenliste ist ein Protokoll privilegierter Arbeit. Die eine Ausnahme sind die eigenen Anfragen eines Benutzers: Die Schlüsselaktionen auf den Identitätsseiten reihen ebenfalls Aufgaben ein, und wer eine eingereiht hat, darf sie öffnen und in /ui/setup/tasks finden, das für ihn nichts anderes auflistet. Die Aufgabe eines anderen ist „nicht gefunden“.
Zwei voneinander unabhängige Dinge müssen gegeben sein, bevor etwas gespeichert werden kann, und die Seite sagt Ihnen, welches davon fehlt:
Der privilegierte Applier muss vorhanden sein — das eigene Debian-Paket
pepsi-httpd-admin, daspepsi-setup-apply.socketund dessen Service enthält. Es ist einRecommends, wird also standardmäßig installiert und kann entfernt werden, ohne Pepsi mitzunehmen; und[pepsi-admin] CONFIG_DBmuss eine Verbindung benennen, die sich als Rollepepsi-configauthentifiziert, denn der Applier lehnt jeden Datensatz ab, der nicht von ihr geschrieben wurde.
Der Applier wird zuerst geprüft, denn ohne Applier würde es nicht helfen, CONFIG_DB einzurichten. Fehlt eines von beiden, stuft sich das Interview auf Nur-Lesen zurück, statt Antworten zur Seite zu legen, die niemand anwenden wird: Jeder Wert wird weiterhin dargestellt, die Eingabefelder kommen disabled/readonly zurück, der Speichern-Knopf ist verschwunden, und eine Einblendung benennt den Grund. Die entsprechenden API-Aufrufe antworten mit 503 setup_applier_unavailable oder 503 setup_write_unavailable (siehe Die administrative API). Die eine bewusste Ausnahme ist das Verwerfen eines Entwurfs — das kann keine privilegierte Änderung auslösen, und ein festgefahrenes Interview muss löschbar bleiben.
Beachten Sie, dass die /ui/config-Seiten von einem fehlenden Applier nicht betroffen sind: Sie schreiben nach pepsi.config_override, eine Datenbanktabelle, nicht nach /etc. Sie brauchen CONFIG_DB und sonst nichts.
Eine Antwort in password-Form wird jedes Mal leer gerendert, nie in ein value=-Attribut zurückgespiegelt, und den Schritt mit leerem Formularfeld abzuschicken lässt das Gespeicherte unangetastet. Der Prüfprotokolleintrag für einen Schritt nennt die Fragen-Bezeichner und überhaupt keine Werte.
21.5.2. Secure Links¶
/ui/secure listet die Nachrichten auf, die das Secure-Link-Portal für Empfänger ohne Schlüssel hält: von wem und für wen sie sind, wann sie ablaufen, wie oft die PIN richtig und falsch eingegeben wurde und ob eine Sperre gilt. /ui/secure/<token> ergänzt das Zugriffsprotokoll.
Die Nachricht selbst steht auf keiner der beiden Seiten und kann es nicht. pepsi-httpd ist das Portal, es ist also die eine Datenbankrolle, der die Chiffratspalte gewährt ist — was genau der Grund ist, warum die Operatorseiten sie nicht lesen. Die Abfragen dahinter benennen jede Spalte außer dieser einen und melden nur ihre Länge, sodass die Disziplin im SQL liegt statt in einem Struct-Feld, das jemand daran gedacht hat wegzulassen. Widerrufen zerstört die einzige Kopie der Nachricht und braucht daher secure:write, eine von secure:read getrennte Fähigkeit.
21.5.3. DNS auf der Domains-Seite¶
/ui/domains zeigt neben jeder bedienten Domain jeden DNS-Eintrag, den Pepsi von ihr erwartet: den Namen, den zu veröffentlichenden Wert, ob das Live-DNS ihn führt, und was zu tun ist, wenn nicht. Der Zonentext am Fuß der Seite ist derselbe Block, den pepsi-setup run ausgibt.
Das Urteil kommt vom Applier, nicht von diesem Server: herauszufinden, was veröffentlicht werden sollte, heißt die Signierschlüssel zu lesen, und es zu vergleichen heißt DNS abzufragen. Die Seite zeigt daher stets, wann die Antwort berechnet wurde, und „DNS jetzt prüfen“ (setup:write) bittet um eine frische, statt die Anfrage an einem Resolver zu blockieren.
21.5.4. Seiten für Endbenutzer¶
Der own:<address>-Geltungsbereich ist definiert und durchgesetzt, und ein Prinzipal, der nur ihn hält, erreicht die Identitäts- und Korrespondentenseiten, eingeengt auf seine eigene Adresse — die Seiten einer anderen Adresse antworten mit 404, und die gemeinsamen Objekte (der Korrespondentenschlüssel-Cache, die Vertrauensanker) werden rundweg abgelehnt. Er erreicht auch das Mail-Protokoll, eingeengt auf seine eigene Adresse; eine Anfrage nach einer anderen Adresse wird dort mit 403 abgelehnt. Auf seinen eigenen Identitäten darf er auch handeln: einen servergeführten Schlüssel anfordern, seinen eigenen öffentlichen Schlüssel registrieren, ein Hochladen auf den Schlüsselserver anfordern, veröffentlichen, zurückziehen und einen Widerruf anfordern – alles außer dem Löschen eines Datensatzes (siehe Schlüsselverwaltung). Eine eigene Endbenutzeroberfläche gibt es nicht; pepsi-stage-edit-settings(1) lässt Benutzer ihre eigenen Einstellungen per E-Mail ändern, und die Steueradresse pepsi-keys@ der Encrypt-Stage lässt sie ihre Schlüssel per E-Mail verwalten.
21.6. Zwei Templating-Engines, zwei Zwecke¶
Pepsi rendert zwei recht verschiedene Arten von Text und verwendet für jede eine andere Engine — versuchen Sie also nicht, eine Konsolenseite so zu überschreiben, wie Sie einen Bounce überschreiben.
Nachrichtentexte von Mails sind Mustache-Vorlagen, als Dateien unter [pepsi] TEMPLATE_DIR ausgeliefert (bounce-<name>.<lang>.body, payment-request.<lang>.body, edit-settings.<lang>.body, wallet.<lang>.body). Sie sind vom Operator bearbeitbar und je Sprache: Von einem Operator wird erwartet, dass er die Prosa, die ein Korrespondent lesen wird, umschreibt und eine Sprache hinzufügt, ohne etwas neu zu bauen. Logikloses Templating ist genau richtig — es gibt nichts zu berechnen, und eine Vorlage, die keinen Code ausführen kann, ist eine, die man einem Operator gefahrlos in die Hand geben kann.
Konsolenseiten sind askama-Vorlagen, in das Binärprogramm einkompiliert und zur Bauzeit typgeprüft gegen die Rust-Structs, die sie rendern. Es gibt zur Laufzeit nichts zu installieren, nichts mit dem Binärprogramm synchron zu halten, und ein umbenanntes Feld ist ein Baufehler statt einer leeren Zelle im Produktivbetrieb. Die Konsole besteht überwiegend aus Tabellen und Bedingungen, was logikloses Templating mühsam macht.
Die praktische Folge: Es gibt keine Möglichkeit, eine Konsolenseite zu überschreiben, und das ist auch nicht vorgesehen. Wenn eine Seite das Falsche sagt, ist das ein zu meldender Fehler und keine zu bearbeitende Datei.
21.7. Kein Scripting, keine entfernten Ressourcen¶
Die Konsole liefert überhaupt kein JavaScript. Jede Aktion ist ein Formular oder ein Link, sodass sie mit deaktiviertem Scripting funktioniert — einschließlich der optionalen automatischen Aktualisierung der Warteschlange, die ein <meta http-equiv="refresh"> ist, das der Leser mit einem Link ein- und ausschaltet, statt eines Timers. Die Diagramme sind kleine handgeschriebene Inline-SVGs, auf dem Server erzeugt, sodass sie in derselben Anfrage rendern wie der Rest der Seite.
Nichts wird von einem anderen Host geladen: ein Stylesheet, in das Binärprogramm einkompiliert und von /ui/static/console.css ausgeliefert. Die Content-Security-Policy verbietet Skripte daher rundweg, statt die eigenen der Konsole zu erlauben, und verbietet Framing (frame-ancestors 'none', mit X-Frame-Options: DENY daneben). Seiten werden mit Cache-Control: no-store gesendet, sodass administrative Daten nicht in einem gemeinsamen Cache oder nach dem Abmelden im Zurück-Knopf liegen.
Cross-Site Request Forgery wird doppelt abgewehrt. Jedes Formular trägt das CSRF-Token der Sitzung als verstecktes Feld, geprüft gegen einen mit der Sitzung gespeicherten Digest; und eine verändernde Anfrage, deren Origin-Header einen anderen Host benennt, wird abgelehnt, bevor ihr Rumpf überhaupt gelesen wird — was das Anmeldeformular abdeckt, das eine Formular, das definitionsgemäß noch keine Sitzung hat.
Werte, die von außen kommen — ein Betreff, der Anzeigename eines Absenders, ein Header-Wert, der Antworttext eines Bounce, die User-ID eines Schlüssels —, werden beim Rendern standardmäßig escaped, und die Tests üben das mit feindlichen Eingaben, statt darauf zu vertrauen. Das einzige Markup, das die Konsole unescaped ausgibt, ist das SVG ihrer eigenen Diagramme, das konstruktionsbedingt Zahlen und sonst nichts enthält.
21.8. Barrierefreiheit¶
Die Konsole ist schlichtes HTML: ein Sprunglink zum Hauptinhalt, eine <h1> je Seite, Tabellen mit <caption> und <th scope=…>, ein Label für jedes Formularfeld und aria-current="page" auf dem Navigationseintrag, auf dem Sie sich befinden. Jede Aktion ist ein echter <button> in einem Formular, sodass sie ohne zu erlernende Tastenkombinationen von der Tastatur aus erreichbar und bedienbar ist, und der Fokusring wird ausdrücklich gezeichnet, weil der voreingestellte vor manchen Hintergründen verschwindet. Hell und dunkel kommen beide aus prefers-color-scheme; es gibt keinen Themenschalter, denn ein Schalter braucht Skript oder ein Cookie, und das Betriebssystem kennt die Antwort bereits.
Die Diagramme tragen aria-hidden und sind nie die einzige Darstellung von irgendetwas: Dieselben Zahlen stehen neben jeder Grafik in der Tabelle.
Die Navigation bietet nur, was Ihre Zugangsdaten erlauben — ein Link, der mit 403 antwortet, ist schlimmer als gar kein Link.
21.9. Die öffentlichen Mailinglistenseiten¶
Eine zweite, getrennte Browseroberfläche, über LISTS = yes freigeschaltet (siehe Drei Listener-Flags, und bei zwei davon lautet der Rat entgegengesetzt) und hier dokumentiert, weil ein Betreiber beiden begegnet:
/listsDas Verzeichnis der beworbenen Listen, nach Domain gruppiert. Eine Liste, deren Attribut
advertisedfalsch ist, fehlt im Verzeichnis und ist weiterhin über ihre URL erreichbar – das ist die Bedeutung des Attributs bei upstream, und es ist keine Sicherheitsgrenze. „Nicht beworben“ klingt wie „versteckt“; das ist es nicht./lists/<list-id>Die Beschreibung einer Liste, ihr
info-Text, ihre Absendeadresse, die Formulare zum An- und Abmelden, ein Link zum Archiv, wenn die Richtlinie eines erlaubt, und die Mitgliederliste nur dann, wennmember_roster_visibilitypublicist./lists/<list-id>/confirm/<token>Worauf ein Bestätigungslink zeigt. Ein ``GET`` schließt nichts ab: es zeigt an, wofür das Token ist, und bittet um ein einzelnes
POST. Mailprogramme, Linkvorschauen und Sicherheitsscanner rufen Links ab, mehrere davon, bevor der Mensch es tut. Das ist ein bewusster Unterschied zur Mailadresse-confirm+<token>, bei der die Ankunft die Bestätigung ist, weil das Token im Umschlag reiste und nur sein Eigentümer es dort hineingelegt haben kann./archives/list/<list@domain>/…Das Archiv: Übersicht, Monat, Thread, Nachricht, Anhang, Suche. Die URL-Formen sind die von HyperKitty, sodass jeder
Archived-At:-Header, den eine migrierte Installation je ausgegeben hat, weiter funktioniert. Siehe Archive.
Drei Dinge über diese Seiten lohnt es sich zu wissen, bevor man sie ausliefert.
Sie liefern überhaupt kein JavaScript aus. Die Content-Security-Policy hat daher kein script-src – nicht einmal 'self' – und sagt default-src 'none'. Der Preis sind die interaktiven Teile von HyperKitty: das Zusammenklappen von Threads ist <details>, es gibt keine Livesuche und keine Antwort auf der Seite. Das wurde bewusst entschieden, und eine Richtlinie, die erlaubt, was nicht benutzt wird, ist eine Richtlinie, die eines Tages erlaubt, was eingeschleust wird.
Nachrichtentexte sind maskierter Text und niemals Markup. Das Attribut archive_rendering_mode von upstream bietet text oder markdown; Pepsi speichert das Attribut und legt es offen – der Attributsatz ist der Kompatibilitätsvertrag – und stellt für beide Werte text dar. Vom Benutzer geliefertes Markdown in unseren eigenen Origin zu rendern ist genau das, was die obige Richtlinie unmöglich machen soll, und ein Markdown-Renderer ist eine HTML-Einschleusungsfläche mit Zwischenschritten.
Absenderadressen werden für anonyme Betrachter verschleiert. Eine Archivseite zeigt alice@... statt der ganzen Adresse. Das Archiv behält die echte – beim Hereinkommen zu verschleiern ließe sich nicht rückgängig machen, und die Bounce-Verarbeitung braucht sie –, es ist also eine Darstellungsentscheidung. Sie ist keine Sicherheitsmaßnahme und soll keine sein: wer auf der Liste ist, hat die Adresse in seiner eigenen Kopie der Nachricht. Was sie verhindert, ist die Masseneinsammlung, die ein öffentliches Archiv zu einer Spamquelle macht.
21.9.1. Formulare, die den Server Post senden lassen¶
Sich anzumelden ist die Aufforderung an den Server, eine Nachricht an eine Adresse zu senden, deren Besitz niemand belegt hat, und das ist ohne Begrenzung ein Belästigungsverstärker – und Pepsi liefert kein CAPTCHA aus, aus derselben Entscheidung, die JavaScript ausschließt. Daher:
jedes solche Formular ist sowohl auf die Zieladresse als auch auf die Clientadresse ratenbegrenzt;
eine zweite Einsendung, während ein Token noch gültig ist, verwendet dieses Token erneut, statt ein weiteres auszugeben, zehn Einsendungen sind also nicht zehn Nachrichten;
die Antwort ist identisch, ob die Adresse schon Mitglied ist oder nicht, das Formular ist also kein Mitgliedschaftsorakel.
Das Letzte ist der Grund, weshalb die Seite „falls diese Adresse angemeldet werden kann, wurde eine Bestätigung an sie gesendet“ sagt und nichts Hilfreicheres.
21.10. Zwei Kontosysteme, und keines gewährt dem anderen etwas¶
Dies ist das am leichtesten missverständliche an der Webebene, es steht daher so klar da wie möglich.
Pepsi hat zwei Arten von Browserkonto, in zwei Tabellen, mit zwei Sätzen von Cookies, und sie haben nichts miteinander zu tun:
Die Konsole des Betreibers |
Das Konto des Listenmitglieds |
|
|---|---|---|
Für wen es ist |
Wer den Mailserver betreibt |
Jeder, der eine Liste abonniert, und die Verwalter und Moderatoren dieser Listen |
Kontotabelle |
|
|
Sitzungstabelle |
|
|
Cookies |
|
|
Listener-Flag |
|
|
Wo es erreichbar sein sollte |
Loopback, über einen SSH-Tunnel |
Das öffentliche Internet |
Angelegt von |
Ein Operator, über |
Registrierung unter |
Was es kann |
Die Konfiguration, die Warteschlange, die Schlüssel und die Protokolle. Nicht die Listen: Diese werden mit pepsi-list(1) oder über die Mailman- |
Die eigenen Abonnements, dazu diejenigen Listen, deren Verwalter oder Moderator die eigene Adresse ist |
Keines ist eine schwächere Fassung des anderen. Ein Betreiberkonto ist Mitglied von nichts, und ein Mitgliedskonto – selbst das eines Listenverwalters – kann die Warteschlange, die Konfiguration oder irgendjemandes Schlüssel nicht lesen. Die beiden sind getrennt, weil ihr Auslieferungsrat entgegengesetzt ist: die Konsole ist das, was das Handbuch aus dem Internet heraushalten lässt, und ein Listenverwalter kann offensichtlich nicht gebeten werden, einen SSH-Tunnel zu öffnen, um eine zurückgehaltene Nachricht freizugeben.
21.10.2. Wenn Sie beide Oberflächen unter einem Hostnamen anbieten¶
Der Assistent warnt davor und kann es nicht verhindern. Cookies sind an den Host gebunden, nicht an den Listener; eine Installation, die ADMIN = yes und LISTS = yes hinter einen Namen legt, hat genau zwei Dinge, die die Oberflächen trennen: die Cookienamen und die Prüfung des Wächters, aus welcher Tabelle eine Sitzung kam.
Das ist eine dünne Linie, und sie ist absichtlich dünn und nicht versehentlich. Sie wird in beide Richtungen getestet – eine Mitgliedssitzung, die einer Betreiberroute vorgelegt wird, wird genauso abgewiesen, wie es kein Cookie würde, und eine Betreibersitzung an einer Mitgliederroute ebenso, ohne dass eines davon einen unterscheidbaren Fehler erzeugt. Bevorzugen Sie dennoch zwei Namen oder besser zwei Listener: ADMIN auf Loopback und LISTS öffentlich ist die Anordnung, die der Rest dieses Kapitels voraussetzt.
21.11. Die Seiten des Mitglieds¶
/lists/registerLegt ein Konto an und sendet einen Bestätigungslink. Nicht orakelnd und begrenzt, wie das Anmeldeformular oben und aus demselben Grund.
/lists/verify/<token>Bestätigt die Adresse und setzt das Passwort, in einem Schritt. Es gibt keine gesonderte Seite „Passwort wählen“, denn ein Bestätigungslink, der nur bestätigt, hinterlässt ein Konto, in das sich niemand anmelden kann.
/lists/sign-in,/lists/sign-outAnmelden mit jeder bestätigten Adresse des Kontos und dem einen Passwort des Kontos. Ein Mitglied darf mehrere Adressen halten – das Modell von upstream ist ein Benutzer mit vielen Adressen –, und genau das lässt „ich habe mich mit meiner Arbeitsadresse angemeldet“ funktionieren. Eine unbestätigte Adresse kann sich nicht anmelden: sie hat nichts belegt.
/lists/resetSendet einen Link zum Zurücksetzen. Einen Link, nie ein Passwort: ein zugesandtes Passwort ist ein Passwort, das für immer in einem Postfach liegt.
pepsi-list owner reset-passwordbleibt die Notluke des Betreibers für den Fall, dass die Post kaputt ist, und es gibt aus statt zu versenden, genau aus diesem Grund./lists/meDie eigenen Adressen und die eigenen Abonnements über alle Listen, jedes mit seinem Zustellmodus und seinem Status.
21.11.1. Warum eine Einstellung nicht dort wirkt, wo man es erwartet¶
Die sechs Zustelleinstellungen werden über eine Kette nachgeschlagen – dieses Abonnement, dann diese Adresse, dann dieses Konto, dann der Standard der Liste selbst –, und jede Ebene darf ungesetzt sein. Ein Mitglied, das eine Einstellung an seinem Konto setzt und feststellt, dass sie für eine Liste nicht wirkt, hat sie daher meist auf einer Ebene gesetzt, die eine genauere überschreibt.
/lists/me zeigt deshalb für jedes Abonnement, von welcher Ebene der Wert kam. Diese Spalte ist die Antwort auf die einzige Frage, die dieser Entwurf zuverlässig hervorruft.
21.12. Die Konsole für Verwalter und Moderatoren¶
/lists/<list-id>/adminDie eine Seite, von der aus man beginnt: die zurückgehaltenen Nachrichten, die Abonnementanfragen und (für einen Verwalter) Links zu den Einstellungsseiten und zur Mitgliederliste.
/lists/<list-id>/admin/held/<request-id>Eine zurückgehaltene Nachricht, mit ihren genauen Bytes als Text angezeigt. Das Annehmen gibt diese Bytes frei, keine Neudarstellung davon – eine zurückgehaltene Nachricht wird gerade deshalb in ihrer Leitungsform aufbewahrt, damit eine Freigabe wirklich zugestellt werden kann, mit unversehrten Signaturen und Anhängen.
Die Seite ist die feindlichste der Website: die Post eines Fremden, dargestellt dort, wo der Klick eines Moderators handeln kann. Der Nachrichtentext ist daher maskierter Text in einem
<pre>, die Richtlinie erlaubt kein Skript, und jede Schaltfläche trägt ein CSRF-Token./lists/<list-id>/admin/settings/<screen>Die schreibbaren Listeneinstellungen, auf elf Seiten in der Gruppierung von Postorius selbst – weil ein migrierender Verwalter eine Einstellung dort sucht, wo sie früher war. Der Hilfetext neben jedem Feld ist der von Postorius, übernommen; siehe die Namensnennung in Mailinglisten.
Die Seiten sind aus derselben Deklaration erzeugt, die die REST-API und die Kommandozeile antreibt; ein Wert, der hier abgelehnt wird, wird dort mit denselben Worten abgelehnt, und eine Einstellung kann nicht an einer Stelle vorhanden und an einer anderen fehlend sein.
Die sechs
*_uri-Vorlagenattribute stehen ebenfalls auf diesen Seiten, sodass ein Verwalter, der seine eigene Willkommensnachricht oder Fußzeile will, die URI dort setzt, wo jede andere Einstellung liegt, und nicht auf einer eigenen Seite./lists/<list-id>/admin/rosterMitglieder, Verwalter und Moderatoren; die Sperren der Liste; und ihre Header-Regeln, mit den serverweiten Sperren und Regeln daneben in Nur-Lese-Form – weil die serverweiten Vorrang haben und ein Verwalter, der eine nie auslösende Regel untersucht, die zuerst ausgelöste sehen muss.
Eine Adresse hinzuzufügen ist die eine Handlung auf dieser Ebene, die jemanden anmeldet, der nicht zugestimmt hat. Es ist eine Verwalterhandlung, sie wird mit dem Verwalter als Handelndem ins Prüfprotokoll geschrieben, und die hinzugefügte Adresse wird nicht als bestätigt markiert: das Wort eines Verwalters ist kein Beleg, dass ein Postfach existiert.
Alles in dieser Konsole wird über dasselbe Protokoll geprüft wie jede Betreiberhandlung, mit dem Konto des Mitglieds als Handelndem. detail trägt nie Nachrichteninhalt – das Annehmen einer zurückgehaltenen Nachricht hält die Anfrage und den Hash der Nachricht fest, nicht den Text – und das Protokoll wird über den Timer [pepsi-admin] EVENT_RETENTION_DAYS des Betreibers ausgedünnt, was es lohnt gegen die gewünschte Aufbewahrungsdauer der Moderationsgeschichte zu prüfen.
21.13. Die Sprache der Oberfläche und warum es nicht zehn sind¶
Die öffentlichen Seiten werden in Englisch, Deutsch oder Französisch ausgeliefert, gewählt anhand des Accept-Language-Headers der Anfrage. Ein Regionalkürzel erhält seine Sprache (de-CH bekommt die deutschen Seiten, weil eine Variante einer Sprache ihr näher ist als Englisch), die q-Reihenfolge des Headers wird beachtet und nicht seine Dokumentreihenfolge, und ein q=0 wird als die Ablehnung gelesen, die es ist – de, en;q=0 bedeutet also Deutsch und nicht „Deutsch, dann irgendetwas“.
Drei Sprachen hier und zehn für die Post ist eine echte Nahtstelle und wird genannt statt verborgen. Die Benachrichtigungsvorlagen – die Willkommensnachricht, die Bestätigungsanfrage, die Bounce-Warnungen – gibt es in zehn Sprachen, weil sie von GNU Mailman kommen, das sie in vierunddreißig hat (siehe Mailinglisten). Die Beschriftungen der Oberfläche sind unsere, und sie gibt es in den drei Sprachen, in denen es das Handbuch gibt. Ein Mitglied, das eine deutsche Willkommensnachricht liest und ihrem Link folgt, landet auf einer deutschen Seite; ein schwedisches landet auf einer englischen. Der Oberfläche eine Sprache hinzuzufügen heißt, etwa 140 kurze Beschriftungen zu übersetzen, und das ist ein Nachmittag und ein Patch, kein Projekt.
Die Wahl wird allein aus dem Header getroffen, vor jedem Datenbankzugriff. Das ist beabsichtigt: eine öffentliche Seite ist für jeden erreichbar, und ein Wort durch das Lesen eines Datensatzes zu wählen würde bedeuten, dass eine nicht authentifizierte Anfrage die Datenbank anfasst, auf einem Server, dessen Worker-Pool eine Verbindung ist. Die gespeicherte Vorliebe eines angemeldeten Mitglieds überschreibt den Header daher nicht.