24. pepsi-httpd¶
The HTTP/HTTPS server: MTA-STS policy, the Web Key Directory and Prometheus metrics.
24.1. Role¶
pepsi-httpd answers HTTP requests for the Pepsi system. It serves the
MTA-STS policy file (so the policy no longer has to be hosted elsewhere), the
Web Key Directory that publishes our users’ OpenPGP keys, and a Prometheus
/metrics page. It is built around a generic dispatch table so further
endpoints can be added without touching the server core. Run it with
pepsi-httpd serve.
HTTP/1.1 (RFC 9112) with the semantics of RFC 9110, over hyper; TLS is
RFC 8446 over rustls. What it serves is defined elsewhere, and each endpoint
has its own governing document:
/.well-known/mta-sts.txt— the policy file of RFC 8461, whose content the relay stages fetch and honour on the way out./.well-known/openpgpkey/…— the OpenPGP Web Key Directory (draft-koch-openpgp-webkey-service, an Internet-Draft rather than an RFC), in both the direct and advanced layouts./mail/config-v1.1.xmland/.well-known/autoconfig/mail/config-v1.1.xml— mail account autoconfiguration (draft-ietf-mailmaint-autoconfig), described below./secure/…— the secure-link portal (The secure-link fallback portal), whose session cookie follows RFC 6265./api/v1/…and/ui— the administrative API and console (The administrative API, The administration console), served only on listeners flaggedADMIN = yes, authenticated with the RFC 6750Bearerscheme or a session cookie.
24.2. Features¶
Multiple listeners. Each
[pepsi-httpd-listener-<name>]section binds one socket exactly like the ingress listeners:SERVE = tcp(BIND_TO/PORT, default port 443),unix(UNIXPATH) orsystemd(socket activation), withMODE = plainortls.SNI certificate selection. A TLS listener chooses its certificate per connection from the client’s SNI host name: each
[pepsi-httpd-cert-<name>]section lists one or moreSNIhost names and aTLS_CERT/TLS_KEYpair. A listener’s ownTLS_CERT/TLS_KEYis the fallback for connections with no SNI match. (MTA-STS requires a certificate valid formta-sts.<domain>.)Key material via systemd credentials. The server runs unprivileged and cannot read certbot’s root-only
/etc/letsencrypt; under systemd it does not need to.pepsi-setupwrites aLoadCredential=drop-in, systemd opens each certificate and key as root when it starts the unit, and the server reads the private copy handed to it under$CREDENTIALS_DIRECTORY. A certificate that still cannot be loaded is skipped with an error rather than taking the whole server down — only a listener left with no usable certificate is fatal.Generic routing. Requests are matched by method and URL shape: a route’s path is a list of segments, each a literal, a named capture of one segment, or a capture of the remainder. Routes are tried in registration order, first match wins, so
GET /.well-known/mta-sts.txt,GET /.well-known/openpgpkey/$DOMAIN/hu/$HASHand a futureGET /foo/$IDare one table entry each.MTA-STS policy.
GET /.well-known/mta-sts.txtreturns the RFC 8461 policy (built from[pepsi] MTA_STS_*and the ingressHOSTNAME) when the requestHostismta-sts.<domain>for a domain inACCEPTED_DOMAINS; any other host yields404.Web Key Directory.
GET /.well-known/openpgpkey/...publishes our own users’ OpenPGP keys, in both the direct and the advanced form, answered from the key store by an indexed(domain, local-part hash)lookup. The response is the binary key withAccess-Control-Allow-Origin: *; the policy file is a zero-length200(its absence makes GnuPG give up before asking for a key); an unknown hash is an empty-bodied404. It serves only identities this deployment holds and has markedpublished— never a cached correspondent key — and it deliberately applies no rate limiting, because the protocol is a public oracle by design. See Key management.Prometheus metrics.
GET /metricsexposes live per-stage gauges (active and paused messages, read straight from the queue) and counters written bypepsi-dispatch(per-stage timeouts, crashes, message count and total processing time for averages, and the global stage/message totals). It is unauthenticated — scrape it over a private/localhost listener.The administrative surface. A listener flagged
ADMIN = yesadditionally serves the/api/v1administrative API (The administrative API) and the/uiadministration console (The administration console). On every other listener those paths answer the ordinary404. The console is a client of the API — same authentication, same scopes, same audit log — and performs no service control.
24.3. Mail client autoconfiguration¶
Setting up a mail account by hand means answering eight questions — two hostnames, two ports, two notions of transport security, two authentication methods — that the person who owns the account is usually the least equipped to answer. draft-ietf-mailmaint-autoconfig fixes that by having the provider publish the answers, and the client fetch them from the address alone.
Pepsi serves the two rungs of that chain a provider is expected to publish. The
Host decides which is being asked for:
https://autoconfig.example.org/mail/config-v1.1.xml (required)
https://example.org/.well-known/autoconfig/mail/config-v1.1.xml (optional)
Both return the same text/xml document, and both are public: the draft
requires it, and the reason is not an oversight but a sequencing problem — a
client has to learn which authentication mechanism to use before it can
authenticate. The document therefore contains no secret, and Pepsi keeps it that
way by naming %EMAILADDRESS%, the placeholder the client substitutes,
rather than any real address. The draft’s optional ?emailaddress= parameter
is accepted and ignored, which also means no request-controlled text is ever
interpolated into a document served from the mail domain’s own origin.
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
<emailProvider id="example.org">
<domain>example.org</domain>
<displayName>Example Mail</displayName>
<displayShortName>Example</displayShortName>
<incomingServer type="imap">
<hostname>mail.example.org</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<outgoingServer type="smtp">
<hostname>mail.example.org</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
</emailProvider>
</clientConfig>
The outgoing half is derived, not configured. Its port, transport security
and authentication are read off the [pepsi-ingress-listener-*] flagged
SUBMISSION = yes, so a deployment that moves submission from STARTTLS on 587
to implicit TLS on 465 gets a corrected document without touching it — the
commonest way a published configuration goes wrong is that it stopped matching
the server, and there is nothing here to forget to update. SMTP_HOST
overrides the derivation for a deployment whose public endpoint is a load
balancer rather than the listener.
The incoming half cannot be derived, because Pepsi does not serve mailboxes;
it hands them to an MDA (pepsi-stage-relay-to-lmtp). So IMAP_HOST or
POP3_HOST must name whatever the deployment pairs with, and until one of
them does, both endpoints answer 404. That is deliberate: a client that finds a
configuration document stops walking the fallback chain, so an incomplete one
leaves the user worse off than none at all.
Publishing needs two things outside the configuration file: an
autoconfig.<domain> DNS record pointing here, and a certificate covering that
name — clients try the https://autoconfig.… URL first, and a certificate
error there is simply a failed lookup. A deployment unwilling to add the name can
rely on the /.well-known/ form, at the cost of being found only by the
clients that try it.
24.4. Configuration¶
[pepsi-httpd]: MAX_CONNECTIONS (default 256). Listeners and certificates
live in [pepsi-httpd-listener-<name>] and [pepsi-httpd-cert-<name>]
sections. The served domains, the MTA-STS mx host and the policy itself come
from the shared [pepsi-ingress] and [pepsi] sections.
See Configuration.
The Web Key Directory needs no configuration of its own — it follows each
identity’s published flag — but the advanced method needs an
openpgpkey.<domain> DNS record per served domain, and the HTTPS listener’s
certificate must cover that name. pepsi-setup run asks certbot for it and
reports any that does not resolve.
[pepsi-autoconfig] configures the autoconfiguration document: IMAP_HOST /
POP3_HOST (either one enables the feature) with their _PORT, _SOCKET
and _AUTH; the optional SMTP_* overrides; and DISPLAY_NAME,
DISPLAY_SHORT_NAME and DOCUMENTATION_URL. See pepsi.conf(5).
24.5. See also¶
Architecture, Key management, pepsi-dispatch, pepsi-keys, pepsi-setup, pepsi.conf(5).