ansible-taler-exchange

Ansible playbook to deploy a production Taler Exchange
Log | Files | Refs | README | LICENSE

README (19479B)


      1 # Ansible Taler Playbooks
      2 
      3 ## Installing dependencies
      4 
      5 Depending on your local installation, you might need
      6 to install the following ansible collection:
      7 
      8 ```
      9 $ ansible-galaxy collection install community.postgresql
     10 ```
     11 
     12 Playbooks that touch vaulted variables need the vault password in
     13 "vault_pass.txt", which is gitignored and shipped only as
     14 "vault_pass.txt.gpg":
     15 
     16 ```
     17 $ gpg -d vault_pass.txt.gpg > vault_pass.txt
     18 ```
     19 
     20 ## Running the main Playbooks
     21 
     22 The canonical playbooks are run via shell scripts in the top-level
     23 directory.
     24 
     25 The operational helpers pass arguments after their positional arguments
     26 directly to `ansible-playbook`. For example:
     27 
     28 ```
     29 $ ./deploy spec --check --diff
     30 $ ./sanction-check spec sanctions.txt --diff
     31 $ BORG_PASSPHRASE=... ./restore spec archive-name --verbose
     32 ```
     33 
     34 Put the host/group first, followed by the sanction list or restore archive
     35 where required, then any Ansible options. `extract-borg-key` and
     36 `setup-pixel-borg` default to `spec` when called without arguments; supply
     37 an explicit target when passing options. Quote option values containing
     38 spaces as usual. Check-mode support depends on the playbook; restore
     39 explicitly rejects check mode. The `test` runner and `stage-activate` do
     40 not forward Ansible options.
     41 
     42 ### Local Podman deployment test
     43 
     44 Run `./test` to build a Debian Trixie container, provision the complete test
     45 deployment, repeat `setup.yml` with `--check --diff`, and exercise the main
     46 systemd units and HTTP APIs.  The script creates a disposable SSH key and
     47 removes the container when it exits.
     48 
     49 The container deliberately runs privileged because the hardened Taler systemd
     50 units need capabilities which Podman otherwise removes.  It also uses the
     51 pregenerated DH parameters and a container-only Certbot substitute which
     52 creates short-lived self-signed certificates: public ACME validation cannot
     53 reach the local test domains.  Neither shortcut is used by real deployments.
     54 
     55 ### Main setup (restore, deploy)
     56 
     57 Database restore is deliberately separate from normal deployment.  It is a
     58 fresh-host recovery operation, not an idempotent part of `deploy`, and it
     59 never chooses an archive implicitly.  First list the Borg repository and
     60 select the exact archive to restore:
     61 
     62 ```
     63 $ BORG_PASSPHRASE=... borg list ssh://borg@pixel.taler-systems.com/~/spec-backup
     64 $ BORG_PASSPHRASE=... ./restore spec 'archive-name-copied-from-list'
     65 ```
     66 
     67 The first argument must be one exact inventory hostname (not a group or host
     68 pattern).  The destination PostgreSQL cluster must contain only the standard
     69 `postgres` database and role.  The restore validates the compressed
     70 `root/postgres-backup.sql.gz` artifact, rejects dumps made by a newer
     71 PostgreSQL major version, restores all databases and roles, and verifies the
     72 exchange database before reporting success.  It does not deploy or start the
     73 application; inspect the recovered host and then run `deploy` separately.
     74 
     75 If SQL restore or verification fails, the partially restored cluster is
     76 considered tainted and must be reinitialized before another attempt.  The
     77 temporary database dump is removed, while a private error log is retained in
     78 `/var/tmp/taler-postgres-restore-*.log` on the target for diagnosis.
     79 
     80 The "deploy" script deploys the latest version of a system on a host.
     81 If you are root@rusty.taler-ops.ch, you may be able to:
     82 
     83 ```
     84 $ ./deploy rusty
     85 ```
     86 
     87 For TOPS production, replace the "rusty" with "spec" to use the actual secrets
     88 for the deployment. For this, you first need to decrypt them:
     89 
     90 ```
     91 $ ./contrib/decrypt inventories/host_vars/spec/prod-secrets.yml.gpg
     92 ```
     93 
     94 Make sure to NEVER commit the decrypted production secrets to Git.
     95 Instead, if you had to edit them, re-encrypt them to all admins:
     96 
     97 ```
     98 $ ./contrib/encrypt inventories/host_vars/spec/prod-secrets.yml
     99 ```
    100 
    101 ### Staging offline master key
    102 
    103 The `test-master.priv` offline master key is intentionally committed to this
    104 repository and referenced by `stage-offline.conf`.  It is a disposable test key
    105 for the staging exchange, where allowing repository users to sign staging
    106 configuration is intentional.  It must never be used for production, for an
    107 exchange holding funds of value, or as the basis for any security assumption.
    108 
    109 ### sanction-check
    110 
    111 This command imports and checks the latest sanction lists:
    112 
    113 ```
    114 $ ./sanction-check $TARGET $LIST
    115 ```
    116 
    117 where "$TARGET" is a host or group from "inventories/default" and $LIST
    118 is the path of the sanction list file on the local disk.
    119 
    120 Note that "SANCTION_LIST" means something else in the "exchange" role,
    121 where it is the name of a file under
    122 "roles/exchange/files/var/lib/taler-exchange/" that is deployed to the
    123 host and referenced from the exchange configuration.
    124 
    125 NOTE: this should still be further automated.
    126 
    127 
    128 ### External node monitoring
    129 
    130 To monitor an exchange node through `tsys-infra/monitoring`, first enroll it
    131 as an external monitoring client using that repository's
    132 `monitoring/README-external.md`. Retrieve both exported YAML files through
    133 an authenticated, encrypted channel. This repository configures only the
    134 exchange node; it does not connect to Sentol or enroll hosts itself.
    135 
    136 Place the exports in the exchange inventory host directory:
    137 
    138 ```
    139 inventories/host_vars/$HOST/monitoring-client.yml
    140 inventories/host_vars/$HOST/monitoring-client-secrets.yml
    141 ```
    142 
    143 The directory uses this repository's inventory hostname, such as `spec`.
    144 The identity inside the export remains the identity enrolled on Sentol;
    145 it need not equal that inventory alias. Use the complete exports without
    146 renaming their fields or combining them with other host variables.
    147 
    148 The proxy bind address defaults to `127.0.0.1` when omitted from the bundle.
    149 Set `monitoring_client.node_exporter.proxy_bind_address` to `"*"` (quoted
    150 in YAML) to listen on all IPv4 and IPv6 interfaces. This requires IPv6
    151 support on the node. Alternatively, use a literal IPv4 or IPv6 address
    152 assigned to the exchange node and reachable from the monitoring server.
    153 Hostnames and bare wildcard addresses (`0.0.0.0` and `::`) are rejected;
    154 use `"*"` for both address families. With `"*"`, the proxy and loopback
    155 backend must use different ports (normally 9100 and 9101). The proxy
    156 still requires mTLS, and the backend remains loopback-only. Keep the
    157 enrolled certificate identity unchanged.
    158 
    159 Commit both files, encrypting the entire secrets file with Ansible Vault
    160 before staging it:
    161 
    162 ```
    163 $ chmod 0600 inventories/host_vars/$HOST/monitoring-client-secrets.yml
    164 $ ansible-vault encrypt inventories/host_vars/$HOST/monitoring-client-secrets.yml
    165 ```
    166 
    167 Ansible uses the existing `vault_pass.txt` configuration. The committed
    168 secrets file must begin with `$ANSIBLE_VAULT;`; plaintext secrets are
    169 rejected when monitoring is enabled. Do not commit the plaintext export
    170 or the vault password. The Ansible controller needs Python 3 and OpenSSL
    171 for bundle validation. As with other vaulted host variables, Ansible may
    172 need the vault password even when monitoring management is disabled.
    173 
    174 Set this in the host's public inventory configuration, then deploy normally:
    175 
    176 ```yaml
    177 enable_monitoring: true
    178 ```
    179 
    180 ```
    181 $ ./deploy "$HOST"
    182 ```
    183 
    184 The `monitoring` role installs Debian's node_exporter with a loopback-only
    185 listener and systemd metrics, a dedicated `node-exporter-proxy` nginx
    186 service requiring Sentol's mTLS client identity, and buffered Fluent Bit
    187 JSON Lines forwarding over HTTPS/mTLS. All connection settings and certificate identities
    188 come from the bundle. TLS material lives in `/etc/taler-monitoring/tls`;
    189 private keys and the enclosing directory are accessible only to root.
    190 The root-running Fluent Bit service reads the journal and client key. The role
    191 requires Debian 13 amd64/arm64 and installs pinned Fluent Bit 5.1.2 from its signed
    192 upstream APT repository. The bundle declares `logs.protocol: jsonline` and an HTTPS
    193 `logs.url` ending in `/jsonline`; old RELP bundles fail preflight.
    194 
    195 Allow Sentol to reach the exported metrics proxy address and port
    196 (normally TCP 9100), and allow the node to reach the exported HTTPS log server
    197 (normally TCP 2514). Firewall and routing configuration is external to
    198 this role. The dedicated proxy leaves exchange webserver sites intact.
    199 
    200 Verify the local services with:
    201 
    202 ```
    203 $ systemctl is-active prometheus-node-exporter node-exporter-proxy fluent-bit
    204 $ curl --fail http://127.0.0.1:9101/metrics >/dev/null
    205 $ logger --tag external-monitoring-enrollment 'Exchange monitoring test'
    206 ```
    207 
    208 Use the exported backend address if it differs. Follow the external-client
    209 runbook to perform an authenticated scrape from Sentol, confirm anonymous
    210 scrapes fail, and find the test message in VictoriaLogs using the exported identity as
    211 `authenticated_host`. Fluent Bit metrics are available through the same mTLS proxy
    212 at `/fluent-bit/metrics`.
    213 
    214 Fluent Bit reads only new journal entries on first activation and resumes its saved
    215 cursor on later starts. Journald uses persistent storage without changing host-specific
    216 retention. `/var/lib/fluent-bit` retains the cursor and filesystem buffer, with a
    217 1 GiB output queue by default (`monitoring_fluent_bit_queue_limit`). Retryable failures
    218 are retried without a count limit; a full queue discards its oldest chunks. Permanent
    219 HTTP errors can discard records, and VictoriaLogs can accept a batch while rejecting
    220 individual malformed lines. Watch both collector drop counters and receiver errors.
    221 Lost responses can cause duplicates; this is not exactly-once or unconditional
    222 lossless delivery. Fluent Bit's own logs remain local to prevent feedback loops.
    223 
    224 Coordinate migration with Sentol's administrator: drain old RELP queues first, deploy
    225 the HTTPS receiver, then deploy this role with the new bundle. TCP 2514 is reused for
    226 HTTPS. First activation skips historical journal entries, so a cutover gap may be lost
    227 centrally. The role stops/removes rsyslog and rsyslog-relp without purging historical
    228 files, removes its forwarding configuration, and disables journal forwarding to syslog.
    229 Do not delete old spool files until any recovery is complete. Preserve the new cursor
    230 and buffer on rollback; restore old server and client configurations together.
    231 
    232 For renewal, retrieve both refreshed exports after `make deploy-sentol`,
    233 replace the local files, encrypt the complete new secrets file before
    234 staging it, commit both, and run `deploy` again. Do not reuse an old key
    235 with a renewed certificate. The role validates both pairs before deployment
    236 and reloads or restarts the affected services when their material changes.
    237 
    238 `enable_monitoring` defaults to false. False skips management and leaves
    239 installed monitoring running. To offboard, follow the Sentol runbook and,
    240 on the exchange node, stop and disable `node-exporter-proxy` and
    241 `prometheus-node-exporter` and `fluent-bit`, then remove the proxy unit
    242 and `/etc/taler-monitoring` (including its private keys) and run
    243 `systemctl daemon-reload`. Set the flag false and remove both inventory
    244 exports. This does not revoke previously issued certificates; complete
    245 the Sentol offboarding steps as well.
    246 
    247 ### Setting up backups (TOPS-only for now)
    248 
    249 First run:
    250 
    251 ```
    252 $ ./extract-borg-key $DEPLOYMENT
    253 ```
    254 
    255 The resulting SSH public key should be added to the borg-account
    256 of the host storing the backup. The borg_host inventory setting selects the target
    257 hostname.
    258 
    259 Once the SSH key is deployed and the backup has been initialized
    260 server-side (see admin-logs/pixel/03-borg.txt), start the daily
    261 backups via:
    262 
    263 ```
    264 $ ./start-borg-backups $DEPLOYMENT
    265 ```
    266 
    267 This will make a backup basically everything relevant to the
    268 deployment, **except** the exchange online signing keys. The
    269 backup will in particular include the system configuration
    270 and a full (gzip-compressed) snapshot of the database.  Thus,
    271 the backups should also suffice to diagnose problems.
    272 
    273 Backups are set to retain daily snapshots of the last 7 days,
    274 weekly snapshots for the last 4 weeks, and monthly snapshots
    275 for the last 6 months.
    276 
    277 
    278 ### Backup (right now)
    279 
    280 To run a backup "immediately" (instead of the daily regular
    281 backups), use:
    282 
    283 ```
    284 $ ./backup $DEPLOYMENT
    285 ```
    286 
    287 
    288 ### Rebooting (into a new kernel)
    289 
    290 This should be done via the 'reboot' playbook which can
    291 be invoked via the
    292 
    293 ```
    294 $ ./reboot $DEPLOYMENT
    295 ```
    296 
    297 script. The reboot playbook first stops all Taler services,
    298 then makes a backup, and then reboots. This should help us
    299 restore to another system in case the host does not come back
    300 online cleanly.
    301 
    302 
    303 ### Testing Locally
    304 
    305 With podman and ansible installed locally one can run:
    306 
    307 ```
    308 $ ./test
    309 ```
    310 
    311 This will begin building the Containerfile in this repo, which is a Debian
    312 base with systemd and a paswordless ssh server configured. Then container
    313 will start, binding port 8022 to 127.0.0.1 on the host. Finally
    314 the setup playbook will be run on the container via ssh.
    315 
    316 
    317 ## Playbooks
    318 
    319 ### backup
    320 
    321 Runs a backup "right now".
    322 
    323 ### borg-ssh-export
    324 
    325 Exports the SSH public keys needed at the remote host for backups.
    326 
    327 ### borg-start
    328 
    329 Enables the borg backup. Should be run after the SSH public keys
    330 exported via borg-ssh-export have been deployed on the receiving
    331 host.
    332 
    333 ### pixel-borg
    334 
    335 Enables receiving (!) backups from pixel. Adds the public key from
    336 pixel so we accept receiving borg backups from pixel.  Note that
    337 pixel still needs to be setup to send the backups.
    338 
    339 ### reboot
    340 
    341 Safely reboots the system by first stopping all Taler services,
    342 then making a backup and only then actually rebooting it.
    343 
    344 ### sanctionlist-check
    345 
    346 Imports the latest sanction list and checks all records against
    347 it.
    348 
    349 ### setup
    350 
    351 The main script that deploys our entire setup.
    352 
    353 ## Roles
    354 
    355 ### auditor
    356 
    357 Deploys the auditor.
    358 
    359 ### backup
    360 
    361 Runs the backup script, making a borg backup of the database and other key parts of the system.
    362 
    363 ### borg-ssh-export
    364 
    365 Exports the SSH public key that must be deployed on the host that is
    366 to receive the backup.
    367 
    368 ### borg-start
    369 
    370 Deploys the backup script and the cron job that runs it daily.
    371 
    372 ### cert
    373 
    374 Issues or renews a LetsEncrypt certificate with certbot and enables the
    375 nginx sites that use it. Included by the roles that own a vhost, not
    376 listed in a playbook directly.
    377 
    378 ### challenger
    379 
    380 Deploys the various challenger services for address verification.
    381 
    382 ### common_packages
    383 
    384 Installs the base system packages we need on all hosts.
    385 Sets up Taler package repo and installs Taler packages.
    386 
    387 ### database
    388 
    389 Installs PostgreSQL and the dependencies needed to administer it through
    390 Ansible, then ensures the database service is running.  Normal deployment
    391 never restores a backup.
    392 
    393 ### database_restore
    394 
    395 Implements the guarded fresh-cluster restore used only by `playbooks/restore.yml`
    396 and `restore`.
    397 
    398 ### devtesting
    399 
    400 Sets up a restricted account for faking wire transfers and inspecting
    401 challenger auth codes. Gated on "dangerously_enable_devtesting" and never
    402 to be enabled in production.
    403 
    404 ### exchange
    405 
    406 Deploys the Taler exchange, then hands over to "exchange_$DEPLOYMENT_KIND"
    407 for the site-specific configuration.
    408 
    409 ### exchange_tops
    410 
    411 Deployment-specific exchange configuration (denominations, KYC rules, fees,
    412 legal documents), selected by DEPLOYMENT_KIND.
    413 
    414 ### exchange-sanctionlist-import
    415 
    416 Imports a new sanction list and checks all existing records against it.
    417 
    418 ### libeufin-nexus
    419 
    420 Deploys libeufin-nexus which connects us to the bank.
    421 
    422 ### monitoring
    423 
    424 Provisions external node monitoring when `enable_monitoring` is true.
    425 
    426 ### pixel_borg
    427 
    428 Configures the host to hosting backups *from* pixel.
    429 
    430 ### reboot
    431 
    432 Reboots the system.
    433 
    434 ### stop_services
    435 
    436 Stops all Taler-related services. Useful for emergency stop and
    437 used as part of the reboot playbook.
    438 
    439 ### webserver
    440 
    441 Configures Nginx reverse proxy (main service, not individual subdomains).
    442 
    443 
    444 ## Checks
    445 
    446 ```
    447 $ ./contrib/test-fact-helpers.sh   # the /bin helpers that generate local facts
    448 ```
    449 
    450 Also run by the CI job in "contrib/ci/jobs/001-build".
    451 
    452 ## Deployment safety and recovery
    453 
    454 Normal deployment still upgrades system packages. It stops existing application
    455 services, targets, timers and sockets before package work, suppresses application
    456 starts through Debian's policy-rc.d mechanism, and starts applications
    457 only after all configuration and database migrations finish. Schedule deployment
    458 as downtime. During the system-wide upgrade, the temporary policy denies Taler
    459 service actions and delegates other actions to the existing administrator policy
    460 (or permits them if none existed). Application package tasks use policy_rc_d: 101.
    461 The original policy is restored even on failure. PostgreSQL and nginx are started
    462 as needed during provisioning.
    463 Cold exchange startup may spend several minutes generating denomination keys.
    464 post_deployment_http_retries defaults to 60 (with a five-second request timeout
    465 and one-second retry delay); systemd unit checks retain their shorter retry limit.
    466 
    467 A failed deployment leaves applications stopped without removing their boot
    468 enablement. Inspect the failed task, correct the problem, then rerun deploy.
    469 Do not reboot or manually start applications to bypass an unfinished migration.
    470 Optional services whose provisioning is skipped are restored if they were active
    471 when that deployment began. After a failed deployment, also inspect any optional
    472 services before returning the host to service.
    473 
    474 Setting dangerously_enable_devtesting to false revokes the managed login keys,
    475 sudo access and helper programs, terminates that account's sessions and disables
    476 the account. Its home-directory data is retained. Setting it true restores the
    477 managed account settings and access.
    478 
    479 The KYC attribute encryption key is preserved in a restricted secret include.
    480 Do not rotate this key as a substitute for moving it: changing it without a data
    481 migration would prevent decryption of existing attributes. Auditor authentication
    482 is stored in a root-only nginx include. Secret tasks suppress output and diffs,
    483 including when removing secrets from previously public configuration files.
    484 
    485 Backup and restore share borg_host and borg_repo inventory defaults, retaining
    486 ssh://borg@pixel.taler-systems.com/~/spec-backup for existing installations. Set
    487 borg_repo in host variables to use another already provisioned repository.
    488 Existing database_restore_borg_repository and database_restore_borg_host overrides
    489 remain supported. No archives are moved automatically.
    490 
    491 The backup script uses /root/.ssh/borg-config and leaves /root/.ssh/config intact.
    492 A lock protects the entire backup operation. Install the updated backup script
    493 between backup runs: an already running older script does not acquire this lock. If cron, backup or reboot
    494 encounters another backup, it exits nonzero (script status 75) without touching
    495 that run's snapshot. Retry after the existing backup finishes; a reboot playbook
    496 aborts before rebooting on this failure. Pruning and compaction are skipped after
    497 an unsuccessful or warning-producing archive creation.
    498 
    499 ## Regression checks
    500 
    501 Run python3 contrib/tests/test_backup.py for isolated concurrency, failure,
    502 cleanup and archive round-trip checks (requires Borg and Python Jinja2).
    503 The extended ./test also exercises real systemd shutdown, package-start
    504 suppression, a pre-2.20 Ansible dependency bootstrap, secret permissions,
    505 devtesting revocation, preservation of unrelated configuration, failed deployment
    506 and recovery. All fixtures run in the disposable container, never an inventory
    507 production host. Monitoring regressions also cover Vault-encrypted onboarding
    508 bundles, preflight failures before application shutdown, authenticated metrics,
    509 JSON Lines delivery, certificate renewal, idempotence, and check mode.