taler-docs

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

paivana-httpd-manual.rst (21476B)


      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 2.1, 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 Architecture overview
     38 ---------------------
     39 
     40 ``paivana-httpd`` does not implement any payment logic of its own.
     41 Instead, every Paivana deployment combines three components:
     42 
     43 1. **The upstream web service.**  This is the existing HTTP service
     44    whose content should be sold (a static website, a cgit service,
     45    a REST API, …).  It does not need to be modified to
     46    work with Paivana.
     47 2. **A GNU Taler merchant backend** (``taler-merchant-httpd``).  The
     48    merchant backend manages templates, creates orders, talks to one
     49    or more Taler exchanges, and ultimately reports back whether a
     50    given order has been paid.  See the
     51    :ref:`Taler Merchant Backend Operator Manual
     52    <taler-merchant-backend-operator-manual>` for full details.
     53 3. **``paivana-httpd`` itself.**  This is the reverse proxy that
     54    gates the upstream service.  It reads a single
     55    :ref:`paivana.conf <Paivana-Configuration>` configuration file
     56    that points at both the merchant backend and the upstream
     57    service.
     58 
     59 Typically a TLS-terminating reverse proxy (Nginx or Apache) is
     60 deployed in front of ``paivana-httpd`` to handle HTTPS and to route
     61 multiple virtual hosts; see :ref:`Paivana-ReverseProxy` below.
     62 
     63 In normal operation the request flow is:
     64 
     65 ::
     66 
     67    client ──▶ Nginx/Apache (TLS) ──▶ paivana-httpd ──▶ upstream
     68     69     70                                   taler-merchant-httpd
     71     72     73                                     Taler exchange
     74 
     75 
     76 Installation
     77 ------------
     78 
     79 Installing from source
     80 ^^^^^^^^^^^^^^^^^^^^^^
     81 
     82 The package sources can be found in our
     83 `download directory <http://ftpmirror.gnu.org/taler/>`__.
     84 
     85 GNU Taler components follow the ``MAJOR.MINOR.MICRO`` version
     86 scheme.  The general rule for compatibility is that ``MAJOR`` and
     87 ``MINOR`` must match across components; exceptions are noted in the
     88 release notes.  For example, ``paivana-httpd`` 1.6.x is expected to
     89 work with ``taler-merchant-httpd`` 1.6.x.  A ``MAJOR`` version of 0
     90 indicates experimental development; in that case you should always
     91 run the *latest* releases of every component together.
     92 
     93 The following packages must be installed before compiling
     94 ``paivana-httpd``:
     95 
     96 - GNUnet (``libgnunetutil``) matching the Taler release
     97 - GNU Taler exchange libraries (``libtalerexchange``,
     98   ``libtalerutil``)
     99 - GNU Taler merchant client library (``libtalermerchant``)
    100 - GNU Taler HTTP daemon helpers (``libtalermhd``,
    101   ``libtalertemplating``)
    102 - libmicrohttpd, libcurl, libjansson, libgcrypt, zlib
    103 
    104 Build and install with:
    105 
    106 .. code-block:: shell-session
    107 
    108    $ ./bootstrap
    109    $ ./configure --prefix=$PREFIX
    110    $ make
    111    $ sudo make install
    112 
    113 
    114 Installing the binary packages on Debian
    115 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    116 
    117 .. include:: frags/installing-debian.rst
    118 
    119 To install ``paivana-httpd`` you can now simply run:
    120 
    121 .. code-block:: shell-session
    122 
    123    # apt install paivana-httpd
    124 
    125 The package does not perform any deployment-specific configuration
    126 work; it only sets up the ``paivana-httpd`` system user, the systemd
    127 service and socket units, and installs example configuration
    128 snippets for Nginx and Apache under ``/etc/nginx/sites-available/``
    129 and ``/etc/apache2/sites-available/``.  You still must configure the
    130 HTTP request routing and the Paivana templates as described below.
    131 
    132 
    133 Installing the binary packages on Ubuntu
    134 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    135 
    136 .. include:: frags/installing-ubuntu.rst
    137 
    138 To install ``paivana-httpd``, run:
    139 
    140 .. code-block:: shell-session
    141 
    142    # apt install paivana-httpd
    143 
    144 As on Debian, the package does not perform any deployment-specific
    145 configuration work.
    146 
    147 
    148 .. _Paivana-Configuration:
    149 
    150 Configuring paivana-httpd
    151 -------------------------
    152 
    153 The main configuration file is ``/etc/paivana/paivana.conf``.  Its
    154 syntax follows the standard GNUnet configuration file format and is
    155 documented in full in :manpage:`paivana.conf(5)`.  Default values
    156 shipped with the package live under
    157 ``/usr/share/paivana/config.d/``; values in ``paivana.conf``
    158 override those defaults.
    159 
    160 All Paivana-specific keys live in the ``[paivana]`` section.  At a
    161 minimum, the file must specify three things:
    162 
    163 - where ``paivana-httpd`` should listen for incoming requests
    164   (``SERVE``, ``UNIXPATH`` / ``PORT``);
    165 - where it should forward paid requests to
    166   (``DESTINATION_BASE_URL``);
    167 - how it should reach the merchant backend
    168   (``MERCHANT_BACKEND_URL`` and ``MERCHANT_ACCESS_TOKEN``).
    169 
    170 A typical configuration that listens on a UNIX domain socket
    171 managed by systemd and forwards to a local upstream server looks
    172 like this:
    173 
    174 .. code-block:: ini
    175 
    176    [paivana]
    177    # Listen on the socket provided by paivana-httpd.socket.
    178    SERVE = unix
    179    UNIXPATH = /run/paivana/httpd/paivana-http.sock
    180    UNIXPATH_MODE = 660
    181 
    182    # Public base URL of this paywall as seen by clients.
    183    # Used when the Host/X-Forwarded-Host headers are unavailable.
    184    BASE_URL = https://paywall.example.com/
    185 
    186    # Upstream service that gets proxied after payment.
    187    DESTINATION_BASE_URL = http://127.0.0.1:8080/
    188 
    189    # Merchant backend used to create and verify orders.
    190    MERCHANT_BACKEND_URL = http://localhost:9966/
    191    MERCHANT_ACCESS_TOKEN = secret-token:CHANGE-ME
    192 
    193    # Stable secret used to MAC the access cookie.
    194    # If unset, a random value is generated at every startup,
    195    # invalidating all previously issued cookies.
    196    SECRET = please-change-this-to-a-long-random-value
    197 
    198    # Resources that should never trigger the paywall, e.g.
    199    # logos, stylesheets or favicons.
    200    WHITELIST = ^/(favicon\.ico|assets/.*|robots\.txt)$
    201 
    202 The exhaustive list of supported keys (``SERVE``, ``PORT``,
    203 ``BIND_TO``, ``UNIXPATH``, ``UNIXPATH_MODE``, ``BASE_URL``,
    204 ``DESTINATION_BASE_URL``, ``MERCHANT_BACKEND_URL``,
    205 ``MERCHANT_BACKEND_UNIX_PATH``, ``MERCHANT_ACCESS_TOKEN``,
    206 ``SECRET``, ``WHITELIST``) is documented in
    207 :manpage:`paivana.conf(5)`.
    208 
    209 If you reach the merchant backend over a UNIX domain socket on the
    210 same host (recommended for a single-machine deployment), replace
    211 the ``MERCHANT_BACKEND_URL`` block with:
    212 
    213 .. code-block:: ini
    214 
    215    MERCHANT_BACKEND_URL = http://localhost/
    216    MERCHANT_BACKEND_UNIX_PATH = /run/taler-merchant/merchant.sock
    217 
    218 .. note::
    219 
    220    ``MERCHANT_ACCESS_TOKEN`` and ``SECRET`` are sensitive values.
    221    Make sure ``paivana.conf`` is only readable by the
    222    ``paivana-httpd`` user.  The Debian package installs the file
    223    accordingly.
    224 
    225 When ``paivana-httpd`` runs behind a trusted reverse proxy
    226 (Nginx/Apache), pass ``-f`` / ``--respect-forwarded-headers`` in the
    227 systemd unit's ``ExecStart=`` so the real client address is taken
    228 from ``X-Forwarded-For``.  See :manpage:`paivana-httpd(1)` for the
    229 remaining command-line flags (in particular ``-g`` to require only
    230 a single payment per site and ``-n`` to disable the paywall for
    231 debugging).
    232 
    233 
    234 Starting and stopping the service
    235 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    236 
    237 The Debian/Ubuntu package ships a socket-activated systemd unit.
    238 After editing ``/etc/paivana/paivana.conf`` enable and start it:
    239 
    240 .. code-block:: shell-session
    241 
    242    # systemctl enable --now paivana-httpd.socket
    243    # systemctl status paivana-httpd
    244 
    245 The socket listens on ``/run/paivana/httpd/paivana-http.sock`` with
    246 group ``www-data``, which lets a co-located Nginx or Apache talk to
    247 the daemon without granting it broader filesystem access.  Logs are
    248 sent to the journal:
    249 
    250 .. code-block:: shell-session
    251 
    252    # journalctl -u paivana-httpd -f
    253 
    254 
    255 .. _Paivana-Templates:
    256 
    257 Configuring Paivana templates
    258 -----------------------------
    259 
    260 ``paivana-httpd`` does not store any per-site pricing or URL-matching rules
    261 itself.  Instead, all rules are expressed as :ref:`merchant templates
    262 <template>` of type ``paivana`` in the merchant backend.  When
    263 ``paivana-httpd`` starts up it asks the merchant backend for every template
    264 configured for the instance identified by ``MERCHANT_BACKEND_URL`` and uses
    265 the ``website_regex`` field of each template to decide which template (and
    266 therefore which payment options) applies to an incoming request URL.
    267 
    268 The corresponding REST API is documented in detail in the
    269 :ref:`Merchant Backend HTTP API <merchant-api>`; see in particular
    270 the
    271 `POST /private/templates
    272 <https://docs.taler.net/core/api-merchant.html#post--private-templates>`__
    273 endpoint and the
    274 :ts:type:`TemplateContractPaivana` definition.
    275 
    276 Prerequisites
    277 ^^^^^^^^^^^^^
    278 
    279 Before creating a template you need:
    280 
    281 - a running ``taler-merchant-httpd`` (see the
    282   :ref:`Launching-the-backend` section of the merchant manual);
    283 - a merchant :ref:`instance <Instance-setup>` with at least one
    284   configured :ref:`bank account <instance-bank-account>`;
    285 - the access token of that instance (used as
    286   ``MERCHANT_ACCESS_TOKEN`` in ``paivana.conf``).
    287 
    288 In the examples below we assume the merchant backend is reachable
    289 at ``http://localhost:9966/``, the default instance is ``default``,
    290 its access token is ``secret-token:sandbox`` and the currency is
    291 ``KUDOS``.  Adjust the URLs, tokens and amounts to match your
    292 deployment.  The
    293 `src/backend/test.sh
    294 <https://git.taler.net/paivana.git/tree/src/backend/test.sh>`__
    295 script that ships with Paivana sets up exactly this minimal
    296 configuration and is a good starting point for experimentation.
    297 
    298 Creating a single global template
    299 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    300 
    301 The simplest Paivana setup uses one template that matches every
    302 URL on the site and charges a fixed price.  This is the
    303 configuration created by ``src/backend/test.sh``:
    304 
    305 .. code-block:: bash
    306 
    307    $ curl -X POST http://localhost:9966/private/templates \
    308        -H 'Authorization: Bearer secret-token:sandbox' \
    309        -H 'Content-Type: application/json' \
    310        -d '{
    311              "template_id": "paivana",
    312              "template_description": "A Paivana template",
    313              "template_contract": {
    314                "template_type": "paivana",
    315                "summary": "Access to example.com",
    316                "website_regex": ".*",
    317                "choices": [ { "amount": "KUDOS:1" } ]
    318              }
    319            }'
    320 
    321 The ``template_type`` must be ``"paivana"``: this allows
    322 ``paivana-httpd`` to pick the template up at startup and
    323 also enables some required logic in the merchant backend.  The
    324 ``website_regex`` is a POSIX extended regular expression that is
    325 matched against the request URL; ``.*`` covers everything.  Each
    326 entry in ``choices`` describes one way the client may pay and is an
    327 :ts:type:`OrderChoice` object (so the paywall can also support
    328 the use of subscription tokens, discount coupons, etc.).
    329 
    330 A successful create returns HTTP ``204 No Content``.  After
    331 creating the template, (re)start ``paivana-httpd`` so that it
    332 re-reads the template list:
    333 
    334 .. code-block:: shell-session
    335 
    336    # systemctl restart paivana-httpd
    337 
    338 Multiple templates with URL-specific pricing
    339 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    340 
    341 When a single site contains content with different prices, define one template
    342 per price bucket and use ``website_regex`` to scope each template to the
    343 matching URLs.  When several templates match the same URL ``paivana-httpd``
    344 picks the first one if finds that matches. Be careful: if multiple templates
    345 match a URL, the result is non-deterministic!
    346 
    347 For example, a news site might charge 2 KUDOS for premium articles
    348 and 50 cents (``KUDOS:0.5``) for standard articles:
    349 
    350 .. code-block:: bash
    351 
    352    $ curl -X POST http://localhost:9966/private/templates \
    353        -H 'Authorization: Bearer secret-token:sandbox' \
    354        -H 'Content-Type: application/json' \
    355        -d '{
    356              "template_id": "premium",
    357              "template_description": "Premium long-form articles",
    358              "template_contract": {
    359                "template_type": "paivana",
    360                "summary": "Premium article on example.com",
    361                "website_regex": "^/premium/.*",
    362                "choices": [ { "amount": "KUDOS:2" } ]
    363              }
    364            }'
    365 
    366    $ curl -X POST http://localhost:9966/private/templates \
    367        -H 'Authorization: Bearer secret-token:sandbox' \
    368        -H 'Content-Type: application/json' \
    369        -d '{
    370              "template_id": "default",
    371              "template_description": "Standard articles",
    372              "template_contract": {
    373                "template_type": "paivana",
    374                "summary": "Standard article on example.com",
    375                "website_regex": "^/standard/.*",
    376                "choices": [ { "amount": "KUDOS:0.5" } ]
    377              }
    378            }'
    379 
    380 Offering multiple payment options
    381 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    382 
    383 The ``choices`` array lets a single template offer several mutually exclusive
    384 ways to pay.  A common pattern is to accept either a cash payment or to sell a
    385 subscription; the wallet shows both options and the customer picks one.  The
    386 third option, where the customer already has a subscription, will be used
    387 automatically by the wallet for subscribers and the customer will not even
    388 have to click to bypass the paywall as a subscriber.  See the merchant manual
    389 for the details of :ref:`OrderChoice <template-choice>` objects.
    390 
    391 .. code-block:: bash
    392 
    393    $ curl -X POST http://localhost:9966/private/templates \
    394        -H 'Authorization: Bearer secret-token:sandbox' \
    395        -H 'Content-Type: application/json' \
    396        -d '{
    397              "template_id": "article",
    398              "template_description": "Single article, paid or via subscription",
    399              "template_contract": {
    400                "template_type": "paivana",
    401                "summary": "Article on example.com",
    402                "website_regex": ".*",
    403                "choices": [
    404                  { "amount": "KUDOS:1",
    405                    "description": "Pay per article" },
    406                  { "amount": "KUDOS:100",
    407                    "description": "Buy subscription",
    408                    "outputs": [ { "token": "monthly-subscription" } ] },
    409                  { "amount": "KUDOS:0",
    410                    "description": "Use my subscription",
    411                    "inputs": [ { "token": "monthly-subscription" } ],
    412                    "outputs": [ { "token": "monthly-subscription" } ] }
    413                ]
    414              }
    415            }'
    416 
    417 Managing templates
    418 ^^^^^^^^^^^^^^^^^^
    419 
    420 Templates can be listed, updated and deleted through the merchant
    421 backend's REST API or through the merchant backend SPA at
    422 ``$MERCHANT_BACKEND_URL/``.  See the merchant manual section on
    423 :ref:`templates <template>` for details, and the API reference for
    424 the relevant endpoints:
    425 
    426 - `GET /private/templates
    427   <https://docs.taler.net/core/api-merchant.html#get--private-templates>`__ —
    428   list all templates of the instance;
    429 - `PATCH /private/templates/$TEMPLATE_ID
    430   <https://docs.taler.net/core/api-merchant.html#patch--private-templates-$TEMPLATE_ID>`__ —
    431   update a template;
    432 - `DELETE /private/templates/$TEMPLATE_ID
    433   <https://docs.taler.net/core/api-merchant.html#delete--private-templates-$TEMPLATE_ID>`__ —
    434   remove a template.
    435 
    436 After any change, restart ``paivana-httpd`` so the new template
    437 list takes effect.
    438 
    439 
    440 .. _Paivana-ReverseProxy:
    441 
    442 Reverse proxy configuration
    443 ---------------------------
    444 
    445 ``paivana-httpd`` itself speaks plain HTTP on a UNIX socket (or a
    446 local TCP port).  In production it is often run behind an Internet-facing
    447 reverse proxy that terminates TLS and forwards requests to the
    448 Paivana socket.  This section gives minimal working examples for
    449 both Nginx and Apache.  The same approach is used for the merchant
    450 backend; see the merchant manual's
    451 :ref:`reverse-proxy-configuration` section for additional
    452 discussion.
    453 
    454 The examples assume the public domain is ``example.com``,
    455 that ``paivana-httpd`` is socket-activated by the shipped
    456 ``paivana-httpd.socket`` unit (so its listening socket lives at
    457 ``/run/paivana/httpd/paivana-http.sock``) and that TLS termination
    458 happens at the reverse proxy.
    459 
    460 .. tab-set::
    461 
    462    .. tab-item:: Nginx
    463 
    464       Place the snippet below in
    465       ``/etc/nginx/sites-available/example.com`` (the
    466       Debian package installs a starter template under
    467       ``/etc/nginx/sites-available/paivana``), then enable it via
    468       ``ln -s ../sites-available/example.com
    469       /etc/nginx/sites-enabled/`` and reload Nginx
    470       (``systemctl reload nginx``).
    471 
    472       .. code-block:: nginx
    473 
    474          server {
    475              listen 443 ssl http2;
    476              listen [::]:443 ssl http2;
    477              server_name example.com;
    478 
    479              ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    480              ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    481 
    482              location / {
    483                  proxy_pass http://unix:/run/paivana/httpd/paivana-http.sock;
    484                  proxy_redirect off;
    485                  proxy_set_header Host              $host;
    486                  proxy_set_header X-Forwarded-For   $remote_addr;
    487                  proxy_set_header X-Forwarded-Host  $host;
    488                  proxy_set_header X-Forwarded-Proto https;
    489              }
    490          }
    491 
    492          server {
    493              listen 80;
    494              listen [::]:80;
    495              server_name example.com;
    496              return 301 https://$host$request_uri;
    497          }
    498 
    499       Make sure ``paivana-httpd`` is started with
    500       ``--respect-forwarded-headers`` (see
    501       :manpage:`paivana-httpd(1)`) so the ``X-Forwarded-For``
    502       header set above is honoured.
    503 
    504    .. tab-item:: Apache
    505 
    506       Enable the required modules once:
    507 
    508       .. code-block:: shell-session
    509 
    510          # a2enmod proxy proxy_http headers ssl
    511          # systemctl reload apache2
    512 
    513       Then drop the following into
    514       ``/etc/apache2/sites-available/example.com.conf``
    515       (the Debian package installs a starter template at
    516       ``/etc/apache2/sites-available/paivana.conf``), enable it
    517       with ``a2ensite example.com`` and reload Apache.
    518 
    519       .. code-block:: apacheconf
    520 
    521          <VirtualHost *:80>
    522              ServerName example.com
    523              Redirect permanent / https://example.com/
    524          </VirtualHost>
    525 
    526          <VirtualHost *:443>
    527              ServerName example.com
    528 
    529              SSLEngine on
    530              SSLCertificateFile      /etc/letsencrypt/live/example.com/fullchain.pem
    531              SSLCertificateKeyFile   /etc/letsencrypt/live/example.com/privkey.pem
    532 
    533              <Location "/">
    534                  ProxyPass        "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/"
    535                  ProxyPassReverse "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/"
    536                  RequestHeader set X-Forwarded-Proto "https"
    537                  RequestHeader set X-Forwarded-Host  "example.com"
    538              </Location>
    539          </VirtualHost>
    540 
    541       As with Nginx, run ``paivana-httpd`` with
    542       ``--respect-forwarded-headers`` so that the client IP is
    543       taken from ``X-Forwarded-For``.
    544 
    545 If you operate both Paivana and the merchant backend on the same
    546 host, you typically expose them under two different hostnames (e.g.
    547 ``example.com`` and ``backend.example.com``); the merchant
    548 backend must *never* be proxied through ``paivana-httpd``, only
    549 the upstream content service should be.
    550 
    551 
    552 Verifying the setup
    553 -------------------
    554 
    555 After completing the steps above, a quick smoke test is to request
    556 a paywalled URL with ``curl``:
    557 
    558 .. code-block:: shell-session
    559 
    560    $ curl -i https://example.com/some-article
    561 
    562 An unpaid request should return ``HTTP/1.1 402 Payment Required`` together
    563 with a Taler-formatted paywall body containing the ``taler://pay/...`` URI of
    564 the freshly created order.  Paying that order with any GNU Taler wallet (see
    565 the `Wallet documentation <https://docs.taler.net/wallet/>`__) and
    566 re-requesting the URL from the same client should then yield the upstream
    567 content unchanged.  If the page is run in a browser, the client-side
    568 JavaScript should automatically trigger the required reload of the page after
    569 the wallet made the payment.
    570 
    571 For interactive debugging, ``paivana-httpd -n`` disables the
    572 paywall and turns the daemon into a transparent reverse proxy;
    573 this is useful to confirm that the network plumbing to the
    574 upstream service works before involving the merchant backend.
    575 See :manpage:`paivana-httpd(1)` for the other runtime flags.