taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

paivana-httpd-manual.rst (52618B)


      1 ..
      2   This file is part of GNU TALER.
      3 
      4   Copyright (C) 2026 Taler Systems SA
      5 
      6   TALER is free software; you can redistribute it and/or modify it under the
      7   terms of the GNU Affero General Public License as published by the Free Software
      8   Foundation; either version 3.0, or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     11   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     12   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     13 
     14   You should have received a copy of the GNU Affero General Public License along with
     15   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     16 
     17   @author Christian Grothoff
     18 
     19 .. _Paivana-httpd:
     20 
     21 Paivana-httpd
     22 =============
     23 
     24 This chapter documents the installation and operation of the Paivana
     25 reverse proxy ``paivana-httpd``.  The reverse proxy sits between the
     26 public Internet and an upstream Web service, intercepting requests
     27 that have not yet been paid for and presenting the client with a
     28 GNU Taler paywall.  Once a payment has been confirmed by the
     29 configured GNU Taler merchant backend, ``paivana-httpd`` forwards
     30 subsequent requests of that client to the upstream service.
     31 
     32 The full list of command-line options is documented in
     33 :manpage:`paivana-httpd(1)`; the configuration file is
     34 documented in :manpage:`paivana.conf(5)`.
     35 
     36 
     37 .. _Paivana-Motivation:
     38 
     39 Motivation and use cases
     40 ------------------------
     41 
     42 ``paivana-httpd`` answers a single question for every incoming
     43 request — *"has the client paid for this?"* — but operators deploy it
     44 for two rather different reasons.  Which one applies to you mostly
     45 changes how you *price* the templates (see :ref:`Paivana-Templates`),
     46 not how you install or run the daemon.
     47 
     48 Selling access: a paywall with revenue
     49 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     50 
     51 The classic use case is to charge money for access to digital content
     52 or services: a news article, a research dataset, an API call, a file
     53 download.  Because Paivana builds on GNU Taler, this comes with
     54 properties that distinguish it from traditional paywalls:
     55 
     56 - **No accounts, no sign-up.**  A visitor pays directly from a Taler
     57   wallet; there is no registration, no login, and no credit-card form.
     58   This removes the friction that kills conversion on per-article
     59   purchases.
     60 - **Micropayments are practical.**  Taler's costs per transaction are
     61   low enough that charging fractions of a cent for a single page is
     62   economically meaningful, enabling true pay-per-article or
     63   pay-per-request business models.
     64 - **Privacy for the buyer.**  Taler is privacy-preserving by design:
     65   the seller (and Paivana) learns that *an* order was paid, not *who*
     66   paid it.  No tracking profile is required to gate the content.
     67 - **Subscriptions and discounts.**  A template's ``choices`` can offer
     68   a one-off payment *or* the purchase/redemption of a subscription
     69   token, so returning subscribers pass the paywall automatically
     70   (see :ref:`Paivana-Templates`).
     71 - **Agentic payments.**  Every paywall response also carries a
     72   machine-readable ``Paivana:`` HTTP header (see
     73   :ref:`Paivana-Customizing`).  An automated client — for example an
     74   AI agent provisioned with a Taler wallet — can therefore pay and
     75   fetch the resource without rendering the HTML page, while the
     76   operator caps financial exposure to whatever was loaded into that
     77   agent's wallet.
     78 
     79 Rate-limiting bots and abuse: DDoS and scraper protection
     80 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     81 
     82 The second use case treats the payment not as a source of revenue but
     83 as a *cost imposed on the client* — economic rate limiting.  Here you
     84 set the price very low (a fraction of a cent, effectively a "CAPTCHA
     85 token"): negligible for a human visitor reading a handful of pages,
     86 but ruinous for a botnet or scraper that wants to issue millions of
     87 requests.  An attacker sending a million requests must spend a million
     88 micropayments; a legitimate user spends almost nothing.
     89 
     90 This is a deliberate alternative to two older defenses whose
     91 cost-asymmetry has eroded:
     92 
     93 - **Proof-of-work puzzles** (as used by tools such as Anubis) ask the
     94   client to burn CPU before being served.  But CPU is cheap and
     95   abundant for a well-resourced attacker or botnet, and the puzzle
     96   burdens low-powered legitimate devices (phones, assistive tech) the
     97   most.
     98 - **CAPTCHAs** ask the client to solve a perception task.  Modern
     99   LLMs and automated solvers now clear text, image and even
    100   "behavioral" CAPTCHAs cheaply and at scale, so they increasingly
    101   inconvenience humans while barely slowing down the bots they were
    102   meant to stop.
    103 
    104 A Taler payment sidesteps both problems: it does not rely on a human
    105 being slow, or on a machine being computationally weak.  It relies on
    106 the one resource a flooding attacker genuinely cannot conjure for free
    107 — money.  Bots *can* solve puzzles and CAPTCHAs; they cannot mint
    108 funds.  For this use case you typically combine a low per-request
    109 price with a subscription-token ``choice`` so that a paying or
    110 authenticated user buys a token once and then sails through, while
    111 anonymous floods keep paying per request.
    112 
    113 .. note::
    114 
    115    The two use cases are not mutually exclusive: the same deployment
    116    can earn revenue on premium URLs and simultaneously throttle abuse
    117    on cheap ones, simply by defining several templates with different
    118    prices and ``website_regex`` scopes.
    119 
    120 
    121 Architecture overview
    122 ---------------------
    123 
    124 ``paivana-httpd`` does not implement any payment logic of its own.
    125 Instead, every Paivana deployment combines three components:
    126 
    127 1. **The upstream web service.**  This is the existing HTTP service
    128    whose content should be sold (a static website, a cgit service,
    129    a REST API, …).  It does not need to be modified to
    130    work with Paivana.
    131 2. **A GNU Taler merchant backend** (``taler-merchant-httpd``).  The
    132    merchant backend manages templates, creates orders, talks to one
    133    or more Taler exchanges, and ultimately reports back whether a
    134    given order has been paid.  See the
    135    :ref:`Taler Merchant Backend Operator Manual
    136    <taler-merchant-backend-operator-manual>` for full details.
    137 3. **``paivana-httpd`` itself.**  This is the reverse proxy that
    138    gates the upstream service.  It reads a single
    139    :ref:`paivana.conf <Paivana-Configuration>` configuration file
    140    that points at both the merchant backend and the upstream
    141    service.
    142 
    143 Typically a TLS-terminating reverse proxy (Nginx or Apache) is
    144 deployed in front of ``paivana-httpd`` to handle HTTPS and to route
    145 multiple virtual hosts; see :ref:`Paivana-ReverseProxy` below.
    146 
    147 In normal operation the request flow is:
    148 
    149 ::
    150 
    151    client ──▶ Nginx/Apache (TLS) ──▶ paivana-httpd ──▶ upstream
    152    153    154                                   taler-merchant-httpd
    155    156    157                                     Taler exchange
    158 
    159 
    160 Installation
    161 ------------
    162 
    163 Installing from source
    164 ^^^^^^^^^^^^^^^^^^^^^^
    165 
    166 The package sources can be found in our
    167 `download directory <http://ftpmirror.gnu.org/taler/>`__.
    168 
    169 GNU Taler components follow the ``MAJOR.MINOR.MICRO`` version
    170 scheme.  The general rule for compatibility is that ``MAJOR`` and
    171 ``MINOR`` must match across components; exceptions are noted in the
    172 release notes.  For example, ``paivana-httpd`` 1.6.x is expected to
    173 work with ``taler-merchant-httpd`` 1.6.x.  A ``MAJOR`` version of 0
    174 indicates experimental development; in that case you should always
    175 run the *latest* releases of every component together.
    176 
    177 The following packages must be installed before compiling
    178 ``paivana-httpd``:
    179 
    180 - GNUnet (``libgnunetutil``) matching the Taler release
    181 - GNU Taler exchange libraries (``libtalerexchange``,
    182   ``libtalerutil``)
    183 - GNU Taler merchant client library (``libtalermerchant``)
    184 - GNU Taler HTTP daemon helpers (``libtalermhd``,
    185   ``libtalertemplating``)
    186 - libmicrohttpd, libcurl, libjansson, libgcrypt, zlib
    187 
    188 Build and install with:
    189 
    190 .. code-block:: shell-session
    191 
    192    $ ./bootstrap
    193    $ ./configure --prefix=$PREFIX
    194    $ make
    195    $ sudo make install
    196 
    197 
    198 Installing the binary packages on Debian
    199 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    200 
    201 .. include:: frags/installing-debian.rst
    202 
    203 To install ``paivana-httpd`` you can now simply run:
    204 
    205 .. code-block:: shell-session
    206 
    207    # apt install paivana-httpd
    208 
    209 The package does not perform any deployment-specific configuration
    210 work; it only sets up the ``paivana-httpd`` system user, the systemd
    211 service and socket units, and installs example configuration
    212 snippets for Nginx and Apache under ``/etc/nginx/sites-available/``
    213 and ``/etc/apache2/sites-available/``.  You still must configure the
    214 HTTP request routing and the Paivana templates as described below.
    215 
    216 
    217 Installing the binary packages on Ubuntu
    218 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    219 
    220 .. include:: frags/installing-ubuntu.rst
    221 
    222 To install ``paivana-httpd``, run:
    223 
    224 .. code-block:: shell-session
    225 
    226    # apt install paivana-httpd
    227 
    228 As on Debian, the package does not perform any deployment-specific
    229 configuration work.
    230 
    231 
    232 .. _Paivana-Configuration:
    233 
    234 Configuring paivana-httpd
    235 -------------------------
    236 
    237 The main configuration file is ``/etc/paivana/paivana.conf``.  Its
    238 syntax follows the standard GNUnet configuration file format and is
    239 documented in full in :manpage:`paivana.conf(5)`.  Files under
    240 ``$PREFIX/share/paivana/config.d/`` are read first and overridden by
    241 it, but Paivana installs none: every option is either required or has
    242 the default stated with it in :manpage:`paivana.conf(5)`.
    243 
    244 All Paivana-specific keys live in the ``[paivana]`` section.  At a
    245 minimum, the file must specify three things:
    246 
    247 - where ``paivana-httpd`` should listen for incoming requests
    248   (``SERVE``, ``UNIXPATH`` / ``PORT``);
    249 - where it should forward paid requests to
    250   (``DESTINATION_BASE_URL``);
    251 - how it should reach the merchant backend
    252   (``MERCHANT_BACKEND_URL`` and ``MERCHANT_ACCESS_TOKEN``).
    253 
    254 A typical configuration that listens on a UNIX domain socket
    255 managed by systemd and forwards to a local upstream server looks
    256 like this:
    257 
    258 .. code-block:: ini
    259 
    260    [paivana]
    261    # Listen on the socket provided by paivana-httpd.socket.
    262    SERVE = unix
    263    UNIXPATH = /run/paivana/httpd/paivana-http.sock
    264    UNIXPATH_MODE = 660
    265 
    266    # Public base URL of this paywall as seen by clients.
    267    # Used when the Host/X-Forwarded-Host headers are unavailable.
    268    BASE_URL = https://paywall.example.com/
    269 
    270    # Upstream service that gets proxied after payment.
    271    DESTINATION_BASE_URL = http://127.0.0.1:8080/
    272 
    273    # Merchant backend used to create and verify orders.
    274    MERCHANT_BACKEND_URL = http://localhost:9966/
    275    MERCHANT_ACCESS_TOKEN = secret-token:CHANGE-ME
    276 
    277    # Stable key material used to MAC the access cookie.  Must be
    278    # generated from a random source, not invented; see below.  If
    279    # unset, a random value is generated at every startup,
    280    # invalidating all previously issued cookies.
    281    SECRET = REPLACE-ME-WITH-GENERATED-RANDOMNESS
    282 
    283    # Resources that should never trigger the paywall, e.g.
    284    # logos, stylesheets or favicons.
    285    WHITELIST = ^/(favicon\.ico|assets/.*|robots\.txt)$
    286 
    287 The exhaustive list of supported keys (``SERVE``, ``PORT``,
    288 ``BIND_TO``, ``UNIXPATH``, ``UNIXPATH_MODE``, ``CONNECTION_LIMIT``,
    289 ``PER_IP_CONNECTION_LIMIT``, ``MAX_REQUEST_SIZE``,
    290 ``REQUEST_BUFFER_MAX``, ``RESPONSE_BUFFER_MAX``, ``UPSTREAM_TIMEOUT``,
    291 ``UPSTREAM_STALL_TIMEOUT``, ``BASE_URL``,
    292 ``DESTINATION_BASE_URL``, ``DESTINATION_UNIXPATH``,
    293 ``MERCHANT_BACKEND_URL``, ``MERCHANT_BACKEND_UNIX_PATH``, ``MERCHANT_ACCESS_TOKEN``,
    294 ``SECRET``, ``WHITELIST``, ``TRUSTED_PROXIES``,
    295 ``TRUSTED_PROXIES6``) is documented in :manpage:`paivana.conf(5)`.
    296 
    297 If you reach the merchant backend over a UNIX domain socket on the
    298 same host (recommended for a single-machine deployment), replace
    299 the ``MERCHANT_BACKEND_URL`` block with:
    300 
    301 .. code-block:: ini
    302 
    303    MERCHANT_BACKEND_URL = http://localhost/
    304    MERCHANT_BACKEND_UNIX_PATH = /run/taler-merchant/merchant.sock
    305 
    306 .. note::
    307 
    308    ``MERCHANT_ACCESS_TOKEN`` and ``SECRET`` are sensitive values.
    309    Make sure ``paivana.conf`` is only readable by the
    310    ``paivana-httpd`` user.  The Debian package installs the file
    311    accordingly.
    312 
    313 When ``paivana-httpd`` runs behind a trusted reverse proxy
    314 (Nginx/Apache), pass ``-f`` / ``--respect-forwarded-headers`` in the
    315 systemd unit's ``ExecStart=`` so the real client address is taken from
    316 the ``Forwarded`` and ``X-Forwarded-For`` headers.  ``-f`` is a
    317 statement that the server in front writes those headers itself, so
    318 configure that server accordingly before you pass it; set
    319 ``TRUSTED_PROXIES`` / ``TRUSTED_PROXIES6`` as well if there is more
    320 than one proxy in the path.  :ref:`Paivana-ReverseProxy` has the
    321 details and working configurations.  See :manpage:`paivana-httpd(1)` for the
    322 remaining command-line flags (in particular ``-g`` to require only
    323 a single payment per site and ``-n`` to disable the paywall for
    324 debugging).
    325 
    326 
    327 Body sizes, buffering and upstream timeouts
    328 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    329 
    330 ``paivana-httpd`` relays bodies as they arrive rather than assembling
    331 them first.  A request or response of any size therefore passes
    332 through, and the client starts receiving the response while the
    333 upstream is still producing it — neither of which was true when each
    334 body had to fit in memory, where anything over 40 MiB failed outright
    335 and nothing reached the client until everything had.
    336 
    337 Two consequences are worth knowing before tuning anything.
    338 
    339 **Memory is bounded by the buffers, not by the content.**  An
    340 in-flight request can hold at most ``REQUEST_BUFFER_MAX`` plus
    341 ``RESPONSE_BUFFER_MAX`` (256 KiB each by default), so the ceiling for
    342 the process is that sum times ``CONNECTION_LIMIT`` — about 256 MiB at
    343 the shipped defaults.  That is a ceiling and not a typical cost: the
    344 buffers are allocated on demand and grown only as far as a request
    345 actually needs, so a page that arrives in one read never occupies more
    346 than that read.  Raise the buffers only to trade memory for throughput
    347 on fast links, and lower ``CONNECTION_LIMIT`` if you raise them a
    348 lot.  When the client cannot keep up, Paivana stops reading
    349 from the upstream; when the upstream cannot keep up, Paivana stops
    350 reading from the client.  Neither ever accumulates.
    351 
    352 **A response can now fail after its status has been sent.**  Once the
    353 upstream's headers have gone out they cannot be retracted, so an
    354 upstream that dies mid-body can no longer be reported as ``502``.
    355 Paivana signals it by breaking the framing instead: a declared
    356 ``Content-Length`` is left unmet, or a chunked response is closed
    357 without its terminating chunk.  Both are failures a conforming client
    358 must detect (RFC 9112, section 8.1.2) — ``curl`` reports exit code
    359 18, for instance — and the event is logged at ``WARNING`` naming the
    360 URL and the byte count reached.
    361 
    362 .. note::
    363 
    364    There is one case where it cannot be signalled at all: an HTTP/1.0
    365    client receiving a response whose length the upstream never
    366    declared.  There the close of the connection *is* the framing, so a
    367    truncated body is indistinguishable from a complete one.  Nothing
    368    at this layer can fix that; it is a property of HTTP/1.0.  It does
    369    not arise for HTTP/1.1 clients, which get chunked framing, nor for
    370    any client when the upstream declares a length.
    371 
    372 The two upstream timeouts bound different things and neither bounds
    373 the request as a whole:
    374 
    375 - ``UPSTREAM_TIMEOUT`` (60 s) is the time to the response *headers*.
    376   Expiry here yields ``504``, and this is the only one that can still
    377   produce a status code.
    378 - ``UPSTREAM_STALL_TIMEOUT`` (60 s) is how long the upstream may move
    379   no bytes in either direction.  Expiry here truncates the response as
    380   described above, if it had started, and yields ``504`` if it had
    381   not.  The clock does not run while Paivana is holding the upstream
    382   back because the client has not caught up, so a slow client is never
    383   mistaken for a stalled upstream.
    384 
    385 Raise ``UPSTREAM_STALL_TIMEOUT`` if your upstream legitimately pauses
    386 for long stretches mid-response — a report generator that computes for
    387 minutes between writes, say.  Do not raise it to accommodate slow
    388 *clients*; that is already handled.
    389 
    390 
    391 Starting and stopping the service
    392 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    393 
    394 The Debian/Ubuntu package ships a socket-activated systemd unit.
    395 After editing ``/etc/paivana/paivana.conf`` enable and start it:
    396 
    397 .. code-block:: shell-session
    398 
    399    # systemctl enable --now paivana-httpd.socket
    400    # systemctl status paivana-httpd
    401 
    402 The socket listens on ``/run/paivana/httpd/paivana-http.sock`` with
    403 group ``www-data``, which lets a co-located Nginx or Apache talk to
    404 the daemon without granting it broader filesystem access.  Logs are
    405 sent to the journal:
    406 
    407 .. code-block:: shell-session
    408 
    409    # journalctl -u paivana-httpd -f
    410 
    411 
    412 .. _Paivana-Whitelisting:
    413 
    414 Whitelisting unpaid resources
    415 -----------------------------
    416 
    417 Most sites contain resources that must remain freely accessible even
    418 to clients that have not paid: the stylesheets, fonts, logos and
    419 scripts referenced by the paywall page itself, a ``favicon.ico``, a
    420 ``robots.txt``, or a health-check endpoint polled by a load balancer.
    421 If these were paywalled, the paywall page could not even render.
    422 
    423 The ``WHITELIST`` configuration key holds a single POSIX *extended*
    424 regular expression that is matched against the request path (which
    425 always begins with ``/``).  When it matches, the request bypasses both
    426 the paywall and the access-cookie check and is proxied straight to the
    427 upstream:
    428 
    429 .. code-block:: ini
    430 
    431    [paivana]
    432    WHITELIST = ^/(favicon\.ico|robots\.txt|assets/.*|.*\.css|.*\.js)$
    433 
    434 A few details worth knowing:
    435 
    436 - The expression is matched against the URL path only, not against the
    437   host or the request method.
    438 - The expression must match the path in its **entirety**.
    439   ``paivana-httpd`` anchors it for you — it compiles ``^(`` *your
    440   expression* ``)$``, so writing your own ``^`` and ``$`` is harmless
    441   but redundant.  What this rules out is a substring match:
    442   ``/assets/`` on its own whitelists nothing, because no path is
    443   exactly that; ``/assets/.*`` is what whitelists that subtree.  The
    444   enclosing group matters for alternations: ``a|b`` behaves as
    445   "exactly ``a`` or exactly ``b``", not as "starts with ``a`` or ends
    446   with ``b``".
    447 - Anchoring is a safety property, not a convenience.  An unanchored
    448   whitelist grants free access to every path merely *containing* the
    449   expression, which a client can arrange deliberately: with a
    450   substring rule, a request for ``/premium/article?x=/robots.txt``
    451   would sail past the paywall.
    452 - Paths longer than 16384 bytes are rejected outright with HTTP ``414
    453   URI Too Long``, before any expression is evaluated.  The cap bounds the
    454   cost of a pathological expression on a path an unpaying client
    455   controls; no legitimate URL comes close to it.
    456 - An invalid regular expression is a fatal configuration error:
    457   ``paivana-httpd`` logs the problem and refuses to start.
    458 - The internal endpoints ``POST /.well-known/paivana`` (payment
    459   callback) and ``GET /.well-known/paivana/templates/...`` (paywall
    460   pages) are always handled by ``paivana-httpd`` itself and are not
    461   affected by the whitelist.
    462 - Running with ``-n`` / ``--no-payment`` whitelists *everything*; the
    463   daemon then behaves as a transparent reverse proxy and never
    464   consults the merchant backend.
    465 
    466 .. note::
    467 
    468    Whitelisting is coarse — it is keyed purely on the URL.  Per-URL
    469    *pricing* (charging different amounts for different paths) is done
    470    with merchant templates and their ``website_regex``, described in
    471    :ref:`Paivana-Templates`, not with ``WHITELIST``.
    472 
    473 
    474 .. _Paivana-AccessControl:
    475 
    476 Access control: per-page vs. site-wide payment
    477 ----------------------------------------------
    478 
    479 Once a client has paid, ``paivana-httpd`` issues a cryptographically
    480 protected access cookie (``Paivana-Cookie``) instead of asking the
    481 merchant backend again on every subsequent request.  The cookie is a keyed hash
    482 over *(expiration time, website, client address)*; it carries no
    483 server-side state, so any of the (possibly several) ``paivana-httpd``
    484 processes can validate it.
    485 
    486 Two aspects of this are operator-visible:
    487 
    488 Scope of a payment
    489 ^^^^^^^^^^^^^^^^^^^
    490 
    491 By default a payment grants access to **the specific URL that was
    492 paid for**: the cookie's ``Path`` is set to that path and the website
    493 is bound into the hash.  This is what you want when each page is sold
    494 individually.
    495 
    496 Passing ``-g`` / ``--global-payment`` changes this so that a single
    497 payment unlocks the **entire site**: the website component is dropped
    498 from the hash and the cookie is issued with ``Path=/``.  Use this when
    499 one purchase (or one subscription) should cover everything behind the
    500 proxy, including for the abuse-mitigation use case where you simply
    501 want a paying client to stop hitting the paywall.
    502 
    503 Binding to the client address
    504 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    505 
    506 The cookie is tied to the client's network address so that it cannot
    507 simply be copied to another machine.  When ``paivana-httpd`` runs
    508 behind a TLS-terminating reverse proxy, the socket peer is always that
    509 proxy, so **every** client would appear to share one address.  Run the
    510 daemon with ``-f`` / ``--respect-forwarded-headers`` in that setup so
    511 the real client address is taken from the ``Forwarded`` and
    512 ``X-Forwarded-For`` headers instead.  Only do this behind a proxy that
    513 writes *both* of those headers itself; a header it merely passes
    514 through is the client's own, and then the client chooses the address
    515 its cookie is bound to.  :ref:`Paivana-ReverseProxy` has the
    516 configurations that get this right.
    517 
    518 Cookie lifetime is derived from the order's ``max_pickup_delay``;
    519 configure it on the template (``max_pickup_duration``) to control how
    520 long a paid client may keep accessing the resource before paying
    521 again.  Because the cookie is keyed by ``SECRET``, leaving ``SECRET``
    522 unset means a new random key is chosen at every startup and all
    523 outstanding cookies are invalidated on restart.
    524 
    525 ``SECRET`` is hashed once to derive that key — no salt, no stretching
    526 — so its own entropy is the only thing protecting it.  An attacker who
    527 has seen a single ``Paivana-Cookie`` can test candidate secrets
    528 offline as fast as it can compute SHA-512, which on ordinary hardware
    529 is billions of guesses per second.  Give ``SECRET`` **at least 128
    530 bits of entropy from a random source**; do not invent a passphrase,
    531 and do not ship the placeholder from the sample configuration above.
    532 Generate one with:
    533 
    534 .. code-block:: shell-session
    535 
    536    # head -c 32 /dev/urandom | base64
    537 
    538 Whoever recovers a weak ``SECRET`` can mint access cookies for any URL
    539 and any client address, and you will not see it happen: the forged
    540 cookies never touch the merchant backend, so nothing distinguishes
    541 them in your logs from paid traffic.
    542 
    543 
    544 .. _Paivana-Redemption:
    545 
    546 Redeeming a payment
    547 -------------------
    548 
    549 The cookie above is handed out at the end of one round-trip that is
    550 worth understanding, because it is the only point at which
    551 ``paivana-httpd`` talks to the merchant backend on behalf of a client,
    552 and because its failure modes are what you will see in the logs.
    553 
    554 When the paywall page has been told by the merchant backend that its
    555 order was paid, it POSTs the order ID (together with the ``nonce``,
    556 ``expiration`` and ``website`` it used to construct the payment) to
    557 ``/.well-known/paivana``.  ``paivana-httpd`` re-derives the session ID
    558 from those values and asks its backend whether that order was really
    559 paid under it, over its own authenticated connection.  Nothing the
    560 client says is taken on trust: the order must be paid and not refunded,
    561 its fulfillment URL must be the ``website`` being claimed (or, where the
    562 order has none, that ``website`` must lie under the configured
    563 ``BASE_URL``), and the requested expiration must not exceed the order's
    564 ``max_pickup_time``.  Only then is the cookie issued.
    565 
    566 This query is a **long poll bounded at five seconds**, and both parts
    567 are deliberate.  A client can be a moment ahead of the backend — it may
    568 even be talking to a different backend process than ``paivana-httpd``
    569 is — so waiting briefly admits an honest client that would otherwise be
    570 turned away just after paying.  Conversely, the endpoint is
    571 unauthenticated and can be posted to before any payment exists, so that
    572 wait is also the time an attacker can pin a connection for, which is
    573 why it is short rather than generous.  The same bound applies to the
    574 outgoing request itself, so a merchant backend that stops responding
    575 cannot hold connections open either.
    576 
    577 The status returned to the client tells you which of these happened:
    578 
    579 ========  ==============================================================
    580 Status    Meaning
    581 ========  ==============================================================
    582 303       Payment confirmed; the ``Paivana-Cookie`` is set and the
    583           client is redirected to the content.
    584 409       The backend says the order was not paid, or it was refunded,
    585           or its contract does not cover the claimed website.  This is
    586           the client's problem, not yours — a stale replay, or a page
    587           that gave up too early.
    588 410       The client asked for access past the order's
    589           ``max_pickup_time``.  Raise ``max_pickup_duration`` on the
    590           template if legitimate clients hit this.
    591 404       The backend does not know this order at all.
    592 504       The backend did not answer within the bound, or could not be
    593           reached.  Check that ``MERCHANT_BACKEND_URL`` is reachable
    594           and that the backend is not itself stalled; the log line
    595           naming the backend and order ID is emitted at ``WARNING``.
    596 502       The backend answered, but with something ``paivana-httpd``
    597           could not use.  This normally means a version mismatch
    598           between the two and is worth reporting.
    599 500       The backend rejected our credentials.  Check
    600           ``MERCHANT_ACCESS_TOKEN``; no client can fix this.
    601 ========  ==============================================================
    602 
    603 A burst of 504s or 500s is therefore an infrastructure problem and a
    604 burst of 409s is not, which is the distinction to key any alerting on.
    605 
    606 
    607 .. _Paivana-Templates:
    608 
    609 Configuring Paivana templates
    610 -----------------------------
    611 
    612 ``paivana-httpd`` does not store any per-site pricing or URL-matching rules
    613 itself.  Instead, all rules are expressed as :ref:`merchant templates
    614 <template>` of type ``paivana`` in the merchant backend.  When
    615 ``paivana-httpd`` starts up it asks the merchant backend for every template
    616 configured for the instance identified by ``MERCHANT_BACKEND_URL`` and uses
    617 the ``website_regex`` field of each template to decide which template (and
    618 therefore which payment options) applies to an incoming request URL.
    619 
    620 The corresponding REST API is documented in detail in the
    621 :ref:`Merchant Backend HTTP API <merchant-api>`; see in particular
    622 the
    623 `POST /private/templates
    624 <https://docs.taler.net/core/api-merchant.html#post--private-templates>`__
    625 endpoint and the
    626 :ts:type:`TemplateContractPaivana` definition.
    627 
    628 Prerequisites
    629 ^^^^^^^^^^^^^
    630 
    631 Before creating a template you need:
    632 
    633 - a running ``taler-merchant-httpd`` (see the
    634   :ref:`Launching-the-backend` section of the merchant manual);
    635 - a merchant :ref:`instance <Instance-setup>` with at least one
    636   configured :ref:`bank account <instance-bank-account>`;
    637 - the access token of that instance (used as
    638   ``MERCHANT_ACCESS_TOKEN`` in ``paivana.conf``).
    639 
    640 In the examples below we assume the merchant backend is reachable
    641 at ``http://localhost:9966/``, the default instance is ``default``,
    642 its access token is ``secret-token:sandbox`` and the currency is
    643 ``KUDOS``.  Adjust the URLs, tokens and amounts to match your
    644 deployment.  The
    645 `src/backend/test.sh
    646 <https://git.taler.net/paivana.git/tree/src/backend/test.sh>`__
    647 script that ships with Paivana sets up exactly this minimal
    648 configuration and is a good starting point for experimentation.
    649 
    650 Creating a single global template
    651 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    652 
    653 The simplest Paivana setup uses one template that matches every
    654 URL on the site and charges a fixed price.  This is the
    655 configuration created by ``src/backend/test.sh``:
    656 
    657 .. code-block:: bash
    658 
    659    $ curl -X POST http://localhost:9966/private/templates \
    660        -H 'Authorization: Bearer secret-token:sandbox' \
    661        -H 'Content-Type: application/json' \
    662        -d '{
    663              "template_id": "paivana",
    664              "template_description": "A Paivana template",
    665              "template_contract": {
    666                "template_type": "paivana",
    667                "summary": "Access to example.com",
    668                "website_regex": ".*",
    669                "choices": [ { "amount": "KUDOS:1" } ]
    670              }
    671            }'
    672 
    673 The ``template_type`` must be ``"paivana"``: this allows
    674 ``paivana-httpd`` to pick the template up at startup and
    675 also enables some required logic in the merchant backend.  The
    676 ``website_regex`` is a POSIX extended regular expression that is
    677 matched against the request URL; ``.*`` covers everything.  Each
    678 entry in ``choices`` describes one way the client may pay and is an
    679 :ts:type:`OrderChoice` object (so the paywall can also support
    680 the use of subscription tokens, discount coupons, etc.).  The
    681 contract as a whole is the :ts:type:`TemplateContractPaivana` object
    682 of the :ref:`merchant backend API <merchant-api>`, which stores
    683 ``website_regex`` but leaves its interpretation to ``paivana-httpd``
    684 — that is, to the rules below.
    685 
    686 Two properties of the matching are easy to get wrong, and both make
    687 the difference between a template that applies and one that silently
    688 never does:
    689 
    690 - The subject is the **absolute URL**, not the path.  It is the
    691   configured ``BASE_URL`` (or the scheme and ``Host`` of the request,
    692   when ``BASE_URL`` is not set) followed by the path — for example
    693   ``https://example.com/premium/article``.  An expression written
    694   against the path alone, such as ``/premium/.*``, therefore matches
    695   nothing.
    696 - The expression must match that URL in its **entirety**.
    697   ``paivana-httpd`` anchors it for you, compiling ``^(`` *your
    698   expression* ``)$``, so a substring rule does not apply to the URLs
    699   containing it.  Writing your own ``^`` and ``$`` remains harmless.
    700   The enclosing group means an alternation such as ``a|b`` is read as
    701   "exactly ``a`` or exactly ``b``".
    702 
    703 So a template meant for one subtree is written either against the
    704 whole URL, ``https://example\.com/premium/.*``, or with a leading
    705 wildcard, ``.*/premium/.*``, if the same template should apply
    706 regardless of the host the site is served under.
    707 
    708 A URL longer than 16384 bytes is answered with HTTP ``414 URI Too Long``
    709 and no template is evaluated for it.  The cap bounds the cost of a
    710 pathological expression on a URL an unpaying client controls.
    711 
    712 A successful create returns HTTP ``204 No Content``.  After
    713 creating the template, (re)start ``paivana-httpd`` so that it
    714 re-reads the template list:
    715 
    716 .. code-block:: shell-session
    717 
    718    # systemctl restart paivana-httpd
    719 
    720 Multiple templates with URL-specific pricing
    721 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    722 
    723 When a single site contains content with different prices, define one template
    724 per price bucket and use ``website_regex`` to scope each template to the
    725 matching URLs.  When several templates match the same URL ``paivana-httpd``
    726 picks the first one if finds that matches. Be careful: if multiple templates
    727 match a URL, the result is non-deterministic!
    728 
    729 For example, a news site might charge 2 KUDOS for premium articles
    730 and 50 cents (``KUDOS:0.5``) for standard articles:
    731 
    732 .. code-block:: bash
    733 
    734    $ curl -X POST http://localhost:9966/private/templates \
    735        -H 'Authorization: Bearer secret-token:sandbox' \
    736        -H 'Content-Type: application/json' \
    737        -d '{
    738              "template_id": "premium",
    739              "template_description": "Premium long-form articles",
    740              "template_contract": {
    741                "template_type": "paivana",
    742                "summary": "Premium article on example.com",
    743                "website_regex": ".*/premium/.*",
    744                "choices": [ { "amount": "KUDOS:2" } ]
    745              }
    746            }'
    747 
    748    $ curl -X POST http://localhost:9966/private/templates \
    749        -H 'Authorization: Bearer secret-token:sandbox' \
    750        -H 'Content-Type: application/json' \
    751        -d '{
    752              "template_id": "default",
    753              "template_description": "Standard articles",
    754              "template_contract": {
    755                "template_type": "paivana",
    756                "summary": "Standard article on example.com",
    757                "website_regex": ".*/standard/.*",
    758                "choices": [ { "amount": "KUDOS:0.5" } ]
    759              }
    760            }'
    761 
    762 Offering multiple payment options
    763 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    764 
    765 The ``choices`` array lets a single template offer several mutually exclusive
    766 ways to pay.  A common pattern is to accept either a cash payment or to sell a
    767 subscription; the wallet shows both options and the customer picks one.  The
    768 third option, where the customer already has a subscription, will be used
    769 automatically by the wallet for subscribers and the customer will not even
    770 have to click to bypass the paywall as a subscriber.  See the merchant manual
    771 for the details of :ref:`OrderChoice <template-choice>` objects.
    772 
    773 .. code-block:: bash
    774 
    775    $ curl -X POST http://localhost:9966/private/templates \
    776        -H 'Authorization: Bearer secret-token:sandbox' \
    777        -H 'Content-Type: application/json' \
    778        -d '{
    779              "template_id": "article",
    780              "template_description": "Single article, paid or via subscription",
    781              "template_contract": {
    782                "template_type": "paivana",
    783                "summary": "Article on example.com",
    784                "website_regex": ".*",
    785                "choices": [
    786                  { "amount": "KUDOS:1",
    787                    "description": "Pay per article" },
    788                  { "amount": "KUDOS:100",
    789                    "description": "Buy subscription",
    790                    "outputs": [ { "token": "monthly-subscription" } ] },
    791                  { "amount": "KUDOS:0",
    792                    "description": "Use my subscription",
    793                    "inputs": [ { "token": "monthly-subscription" } ],
    794                    "outputs": [ { "token": "monthly-subscription" } ] }
    795                ]
    796              }
    797            }'
    798 
    799 Managing templates
    800 ^^^^^^^^^^^^^^^^^^
    801 
    802 Templates can be listed, updated and deleted through the merchant
    803 backend's REST API or through the merchant backend SPA at
    804 ``$MERCHANT_BACKEND_URL/``.  See the merchant manual section on
    805 :ref:`templates <template>` for details, and the API reference for
    806 the relevant endpoints:
    807 
    808 - `GET /private/templates
    809   <https://docs.taler.net/core/api-merchant.html#get--private-templates>`__ —
    810   list all templates of the instance;
    811 - `PATCH /private/templates/$TEMPLATE_ID
    812   <https://docs.taler.net/core/api-merchant.html#patch--private-templates-$TEMPLATE_ID>`__ —
    813   update a template;
    814 - `DELETE /private/templates/$TEMPLATE_ID
    815   <https://docs.taler.net/core/api-merchant.html#delete--private-templates-$TEMPLATE_ID>`__ —
    816   remove a template.
    817 
    818 After any change, restart ``paivana-httpd`` so the new template
    819 list takes effect.
    820 
    821 
    822 .. _Paivana-Customizing:
    823 
    824 Customizing the paywall page
    825 ----------------------------
    826 
    827 The merchant templates of the previous section decide *which* URLs
    828 cost *how much*.  This section is about the *look* of the page an
    829 unpaid visitor sees — the HTML the daemon returns with the
    830 ``402 Payment Required`` status.
    831 
    832 How the page is rendered
    833 ^^^^^^^^^^^^^^^^^^^^^^^^^^
    834 
    835 The paywall body is produced from a `Mustache
    836 <https://mustache.github.io/>`__ template named ``paywall`` that
    837 ``paivana-httpd`` loads, via ``libtalertemplating``, from
    838 
    839 ::
    840 
    841    $PREFIX/share/paivana/templates/
    842 
    843 Templates are language-specific: the file is named
    844 ``paywall.$LANG.must`` and the daemon picks a variant based on the
    845 client's ``Accept-Language`` header.  The package ships an English
    846 default, ``paywall.en.must``.  To offer the paywall in additional
    847 languages, drop further ``paywall.<lang>.must`` files into the same
    848 directory.  Rendered responses are cached per language and per
    849 content-encoding (and carry ``Cache-Control: public`` and a matching
    850 ``Vary`` header), which is what lets the paywall be served as a
    851 scalable, mostly-static page even under load.
    852 
    853 When rendering, ``paivana-httpd`` passes the following values into the
    854 template:
    855 
    856 ========================  ====================================================
    857 Variable                  Meaning
    858 ========================  ====================================================
    859 ``merchant_backend``      Base URL of the merchant backend (the page
    860                           long-polls it for payment completion).
    861 ``template_id``           ID of the matched merchant template.
    862 ``summary``               Human-readable summary from the template,
    863                           if any.
    864 ``choices``               Array of payment options (:ts:type:`OrderChoice`).
    865 ``has_choices``           True if more than one payment option exists.
    866 ``default_choice``        The first entry of ``choices``.
    867 ``max_pickup_delay``      Seconds the order may be picked up within.
    868 ========================  ====================================================
    869 
    870 The shipped page uses these to draw a ``taler://`` QR code and a
    871 "Pay now" link, long-poll the backend, and reload the original URL
    872 once the wallet confirms.  The non-visual integration point — useful
    873 for automated and agentic clients — is the ``Paivana:`` HTTP response
    874 header, which carries the ``taler://pay-template/...`` URI directly, so
    875 a client need not parse the HTML to find out how to pay.
    876 
    877 Editing the appearance
    878 ^^^^^^^^^^^^^^^^^^^^^^^^
    879 
    880 For small tweaks — colours, wording, your logo, a link back to your
    881 site — you can edit the installed ``paywall.en.must`` directly; it is
    882 ordinary HTML/CSS with Mustache ``{{ ... }}`` placeholders for the
    883 variables above.  Keep the ``<script>`` block intact: it is what
    884 performs the long-poll and the post-payment reload.
    885 
    886 The shipped ``paywall.en.must`` is in fact *generated* at build time
    887 from a Jinja2 source, ``src/frontend/paywall.en.must.j2``, which inlines
    888 the client-side logic from ``src/frontend/paywall.js`` and a bundled QR
    889 library.  (To avoid clashing with Mustache's ``{{ }}``, the Jinja2
    890 source uses ``@@ ... @@`` and ``@< ... >@`` delimiters.)  If you want to
    891 change the JavaScript behaviour rather than just the styling, edit the
    892 ``.j2`` source and rebuild — the Meson ``custom_target`` regenerates the
    893 ``.must`` file and installs it into the templates directory shown
    894 above.
    895 
    896 
    897 .. _Paivana-ReverseProxy:
    898 
    899 Reverse proxy configuration
    900 ---------------------------
    901 
    902 ``paivana-httpd`` itself speaks plain HTTP on a UNIX socket (or a
    903 local TCP port).  In production it is often run behind an Internet-facing
    904 reverse proxy that terminates TLS and forwards requests to the
    905 Paivana socket.  This section gives minimal working examples for
    906 both Nginx and Apache.  The same approach is used for the merchant
    907 backend; see the merchant manual's
    908 :ref:`reverse-proxy-configuration` section for additional
    909 discussion.
    910 
    911 The examples assume the public domain is ``example.com``,
    912 that ``paivana-httpd`` is socket-activated by the shipped
    913 ``paivana-httpd.socket`` unit (so its listening socket lives at
    914 ``/run/paivana/httpd/paivana-http.sock``) and that TLS termination
    915 happens at the reverse proxy.
    916 
    917 In this setup ``paivana-httpd`` has to run with ``-f`` /
    918 ``--respect-forwarded-headers``: a UNIX-domain peer has no address of
    919 its own, so without ``-f`` there is no client address at all, the
    920 access cookie cannot be bound to one, and
    921 ``POST /.well-known/paivana`` fails.  What ``-f`` means, though, is
    922 that the client address becomes whatever the forwarding headers say it
    923 is — and that address is what the cookie is bound to and what every
    924 address-based decision uses.  So it is worth being precise about which
    925 part of those headers is believed.
    926 
    927 ``paivana-httpd`` reads the chain from the **right**.  The rightmost
    928 element was written by the peer it accepted the connection from, which
    929 ``-f`` asserts is a proxy of yours; every step further leftwards is
    930 hearsay written by the node to its right, and is permitted only if
    931 that node is listed in ``TRUSTED_PROXIES`` / ``TRUSTED_PROXIES6``.
    932 With no such list configured the walk stops on its first step: trust
    933 extends exactly one hop, and the element the front server itself wrote
    934 is the client.  Anything a client prepends to the header sits to the
    935 left of that and can never be promoted.
    936 
    937 The failure mode is therefore not a client that prepends, but a header
    938 the front server never writes at all — because then the rightmost
    939 element is the client's own, and it is believed.  Both Nginx and
    940 Apache forward a request header they were not told to touch, and
    941 neither generates an RFC 7239 ``Forwarded`` header on its own.
    942 ``paivana-httpd`` *prefers* ``Forwarded`` over ``X-Forwarded-For``
    943 where both are present, so a front server that carefully sets
    944 ``X-Forwarded-For`` and ignores ``Forwarded`` is still handing every
    945 client its choice of address: the client sends one ``Forwarded``
    946 element, that element is the whole chain, it is the rightmost, and it
    947 wins over everything the proxy set.  A paid cookie is then worth
    948 nothing, since anyone can present the address it was issued to.
    949 
    950 Deal with **both** headers, on every request.  Whether you overwrite
    951 or append matters less than that you write: appending — Nginx's
    952 ``$proxy_add_x_forwarded_for``, Apache's ``ProxyAddHeaders`` — also
    953 puts the real peer rightmost and is what you want when your own proxy
    954 sits behind another one.  At the outermost hop there is nothing to
    955 preserve, so the configurations below simply set.
    956 
    957 They are the ones shipped with the Debian package, with TLS
    958 termination added; their comments explain each choice.
    959 
    960 .. tab-set::
    961 
    962    .. tab-item:: Nginx
    963 
    964       The package installs this at
    965       ``/etc/nginx/sites-available/paivana``.  Adapt ``server_name``
    966       and the certificate paths, then enable it via ``ln -s
    967       ../sites-available/paivana /etc/nginx/sites-enabled/`` and
    968       reload Nginx (``systemctl reload nginx``).
    969 
    970       .. code-block:: nginx
    971 
    972          # RFC 7239 node identifier for the peer we accepted from.  nginx has
    973          # no built-in variable for this: an IPv6 address has to be bracketed
    974          # and therefore quoted (RFC 7239 §6), and a peer with no address is
    975          # "unknown" (§6.3).
    976          map $remote_addr $paivana_forwarded_elem {
    977            ~^[0-9.]+$        "for=$remote_addr";
    978            ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";
    979            default           "for=unknown";
    980          }
    981 
    982          server {
    983            listen 443 ssl;
    984            listen [::]:443 ssl;
    985            server_name example.com;
    986 
    987            ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    988            ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    989 
    990            location / {
    991              proxy_pass http://unix:/run/paivana/httpd/paivana-http.sock;
    992              proxy_redirect off;
    993              proxy_set_header Host $host;
    994 
    995              # paivana-httpd is started with -f (see paivana-httpd.service), so
    996              # it takes the client address for the access cookie from the
    997              # headers set here.  What makes that sound is that we set every
    998              # one of them: paivana-httpd reads the chain from the right, so
    999              # the element that counts is the last one written -- ours.  A
   1000              # header we do not set is forwarded as the client sent it, and
   1001              # then the element that counts is the client's own.
   1002              #
   1003              # $remote_addr rather than $proxy_add_x_forwarded_for because
   1004              # this is the outermost hop: the peer we accepted from IS the
   1005              # client, and there is no earlier chain worth preserving.  If
   1006              # this nginx is itself behind another proxy, switch to
   1007              # $proxy_add_x_forwarded_for -- appending is equally sound, the
   1008              # peer still ends up rightmost -- and list that hop in
   1009              # TRUSTED_PROXIES so paivana-httpd may step over it.
   1010              proxy_set_header X-Forwarded-For   $remote_addr;
   1011              proxy_set_header X-Forwarded-Proto $scheme;
   1012              proxy_set_header X-Forwarded-Host  $host;
   1013              proxy_set_header X-Forwarded-Port  $server_port;
   1014 
   1015              # RFC 7239.  paivana-httpd PREFERS this over the X-Forwarded-*
   1016              # headers above, which are kept for origins that only speak those.
   1017              # Setting it is not optional: nginx neither generates a Forwarded
   1018              # header nor strips the client's, so without this line a client
   1019              # can send one element of its own, have it be the whole chain,
   1020              # and outrank everything set above.  Behind another proxy,
   1021              # replace this with the appending form from nginx.org's "Using
   1022              # the Forwarded header", which validates $http_forwarded before
   1023              # extending it.
   1024              proxy_set_header Forwarded "$paivana_forwarded_elem;proto=$scheme;host=$host";
   1025            }
   1026          }
   1027 
   1028          server {
   1029            listen 80;
   1030            listen [::]:80;
   1031            server_name example.com;
   1032            return 301 https://$host$request_uri;
   1033          }
   1034 
   1035    .. tab-item:: Apache
   1036 
   1037       Enable the required modules once:
   1038 
   1039       .. code-block:: shell-session
   1040 
   1041          # a2enmod proxy proxy_http headers ssl
   1042          # systemctl reload apache2
   1043 
   1044       The package installs the ``<Location>`` block below at
   1045       ``/etc/apache2/sites-available/paivana.conf``; it is shown here
   1046       inside a TLS-terminating virtual host.  Enable it with
   1047       ``a2ensite paivana`` and reload Apache.
   1048 
   1049       .. code-block:: apacheconf
   1050 
   1051          <VirtualHost *:80>
   1052              ServerName example.com
   1053              Redirect permanent / https://example.com/
   1054          </VirtualHost>
   1055 
   1056          <VirtualHost *:443>
   1057              ServerName example.com
   1058 
   1059              SSLEngine on
   1060              SSLCertificateFile      /etc/letsencrypt/live/example.com/fullchain.pem
   1061              SSLCertificateKeyFile   /etc/letsencrypt/live/example.com/privkey.pem
   1062 
   1063              <Location "/">
   1064              # paivana-httpd is started with -f (see paivana-httpd.service), so it
   1065              # takes the client address for the access cookie from the forwarding
   1066              # headers, reading the chain from the right.  mod_proxy's
   1067              # ProxyAddHeaders (on by default) appends the real client to
   1068              # X-Forwarded-For, which puts it rightmost -- but mod_proxy does not
   1069              # touch Forwarded, and paivana-httpd prefers Forwarded.  A
   1070              # client-supplied one would arrive whole and be believed.  Drop the
   1071              # client's copies of all of them first, so that only what Apache and
   1072              # the line below produce reaches paivana-httpd.
   1073              #
   1074              # If this Apache is itself behind another proxy, remove these and
   1075              # configure mod_remoteip (RemoteIPHeader / RemoteIPInternalProxy) for
   1076              # that hop instead, listing it in TRUSTED_PROXIES.
   1077              RequestHeader unset X-Forwarded-For
   1078              RequestHeader unset X-Forwarded-Proto
   1079              RequestHeader unset X-Forwarded-Host
   1080              RequestHeader unset X-Forwarded-Port
   1081              RequestHeader unset Forwarded
   1082 
   1083              # RFC 7239, which paivana-httpd prefers over the X-Forwarded-* headers
   1084              # mod_proxy adds.  Apache emits no Forwarded of its own, so build the
   1085              # element here.  It must be the "expr=" form: mod_headers' %{NAME}e
   1086              # reads subprocess_env, which is still empty when RequestHeader runs,
   1087              # so the %{REMOTE_ADDR}e spelling silently yields "for=(null)".  That
   1088              # is not a weaker paywall but a dead one -- an element naming no
   1089              # address sends paivana-httpd back to the socket peer, which over a
   1090              # UNIX socket is no address at all, and no cookie can be issued.
   1091              # Note that %{REMOTE_ADDR} yields an unbracketed IPv6 address where
   1092              # RFC 7239 §6 asks for for="[...]"; paivana-httpd accepts both.
   1093              RequestHeader set Forwarded "expr=for=%{REMOTE_ADDR};proto=%{REQUEST_SCHEME};host=%{HTTP_HOST}"
   1094 
   1095              ProxyPass        "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/"
   1096              ProxyPassReverse "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/"
   1097              </Location>
   1098          </VirtualHost>
   1099 
   1100       There is deliberately no ``RequestHeader set X-Forwarded-Proto``
   1101       here.  ``mod_proxy`` does not add that header itself, and the
   1102       scheme ``paivana-httpd`` uses comes from the ``proto=``
   1103       parameter of the ``Forwarded`` element built above, which it
   1104       prefers anyway.
   1105 
   1106 Neither configuration needs ``TRUSTED_PROXIES`` to be safe — with one
   1107 proxy in front, the one hop of trust that ``-f`` grants is exactly the
   1108 hop there is.  ``TRUSTED_PROXIES`` and ``TRUSTED_PROXIES6`` are what
   1109 you need when the chain is **longer** than that: a CDN or a load
   1110 balancer in front of your Nginx, whose element your Nginx appends to.
   1111 Listing those hops is what permits the walk to keep stepping leftwards
   1112 past them and reach the client that is genuinely at the far end:
   1113 
   1114 .. code-block:: ini
   1115 
   1116    [paivana]
   1117    TRUSTED_PROXIES  = 10.0.0.0/8;192.168.0.0/16;
   1118    TRUSTED_PROXIES6 = 2001:db8::/32;
   1119 
   1120 The walk still stops at the first element written by a node you did
   1121 not list, so a client that prepends entries of its own gains nothing
   1122 by it however long the list gets.  Listing too much is the real risk:
   1123 every network you add is a network whose members may name any client
   1124 address they like.  Keep the list to the proxies you operate, and note
   1125 that a UNIX-domain peer needs no entry at all — it has no address to
   1126 match, is by construction on this host, and access to it is governed
   1127 by ``UNIXPATH_MODE``.  The syntax has sharp edges (entries are
   1128 terminated as well as separated by ``;``, and ``TRUSTED_PROXIES6``
   1129 tolerates no spaces); :manpage:`paivana.conf(5)` documents them.
   1130 
   1131 One thing the walk does not cover, and the reason the front server's
   1132 configuration still matters after you have set all this: ``-f`` also
   1133 lets the forwarding headers supply the scheme and authority that
   1134 ``paivana-httpd`` builds its own URLs from, when ``BASE_URL`` is not
   1135 set.  Those are read as the **leftmost** ``proto=`` / ``host=`` (or
   1136 ``X-Forwarded-Proto`` / ``-Host`` / ``-Port``) value, which is not the
   1137 element the trusted-proxy walk selects and is not vetted against it.
   1138 Setting ``BASE_URL`` to your public HTTPS URL closes that off entirely
   1139 and is worth doing whether or not you also list your proxies.
   1140 
   1141 Conversely, do not pass ``-f`` to a ``paivana-httpd`` that clients can
   1142 reach directly.  There it is the client, not a proxy, that is setting
   1143 those headers, and ``-f`` is the assertion that this is not so.
   1144 
   1145 If you operate both Paivana and the merchant backend on the same
   1146 host, you typically expose them under two different hostnames (e.g.
   1147 ``example.com`` and ``backend.example.com``); the merchant
   1148 backend must *never* be proxied through ``paivana-httpd``, only
   1149 the upstream content service should be.
   1150 
   1151 
   1152 Verifying the setup
   1153 -------------------
   1154 
   1155 After completing the steps above, a quick smoke test is to request
   1156 a paywalled URL with ``curl``:
   1157 
   1158 .. code-block:: shell-session
   1159 
   1160    $ curl -i https://example.com/some-article
   1161 
   1162 An unpaid request will first redirect (302 Found) to the static
   1163 paywall page at ``/.well-known/paivana/templates/{id}#{base64_website}``
   1164 which will then return a ``HTTP/1.1 402 Payment Required`` together
   1165 with a Taler-formatted paywall body containing the ``taler://pay/...`` URI of
   1166 the freshly created order.  Paying that order with any GNU Taler wallet (see
   1167 the `Wallet documentation <https://docs.taler.net/wallet/>`__) and
   1168 re-requesting the URL from the same client should then yield the upstream
   1169 content unchanged.  If the page is run in a browser, the client-side
   1170 JavaScript should automatically trigger the required reload of the page after
   1171 the wallet made the payment.
   1172 
   1173 For interactive debugging, ``paivana-httpd -n`` disables the
   1174 paywall and turns the daemon into a transparent reverse proxy;
   1175 this is useful to confirm that the network plumbing to the
   1176 upstream service works before involving the merchant backend.
   1177 See :manpage:`paivana-httpd(1)` for the other runtime flags.