README (24524B)
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 Deployments keep `/var/lib/taler-deployment/incomplete` until configuration 88 handlers and post-deployment checks have succeeded. If a run fails or is 89 interrupted, the next `deploy` reapplies nginx and enabled monitoring service 90 configuration even when the files are unchanged. This recovers restart 91 notifications lost by the previous Ansible process. Applications still stay 92 stopped on deployment failure; check mode neither clears the marker nor 93 restarts services. Normal successful reruns keep the usual change-triggered 94 handler behavior. 95 96 For TOPS production, replace the "rusty" with "spec" to use the actual secrets 97 for the deployment. For this, you first need to decrypt them: 98 99 ``` 100 $ ./contrib/decrypt inventories/host_vars/spec/prod-secrets.yml.gpg 101 ``` 102 103 Make sure to NEVER commit the decrypted production secrets to Git. 104 Instead, if you had to edit them, re-encrypt them to all admins: 105 106 ``` 107 $ ./contrib/encrypt inventories/host_vars/spec/prod-secrets.yml 108 ``` 109 110 ### Staging offline master key 111 112 The `test-master.priv` offline master key is intentionally committed to this 113 repository and referenced by `stage-offline.conf`. It is a disposable test key 114 for the staging exchange, where allowing repository users to sign staging 115 configuration is intentional. It must never be used for production, for an 116 exchange holding funds of value, or as the basis for any security assumption. 117 118 ### sanction-check 119 120 This command imports and checks the latest sanction lists: 121 122 ``` 123 $ ./sanction-check $TARGET $LIST 124 ``` 125 126 where "$TARGET" is a host or group from "inventories/default" and $LIST 127 is the path of the sanction list file on the local disk. 128 129 Note that "SANCTION_LIST" means something else in the "exchange" role, 130 where it is the name of a file under 131 "roles/exchange/files/var/lib/taler-exchange/" that is deployed to the 132 host and referenced from the exchange configuration. 133 134 NOTE: this should still be further automated. 135 136 137 ### External node monitoring 138 139 To monitor an exchange node through `tsys-infra/monitoring`, first enroll it 140 as an external monitoring client using that repository's 141 `monitoring/README-external.md`. Retrieve both exported YAML files through 142 an authenticated, encrypted channel. This repository configures only the 143 exchange node; it does not connect to Sentol or enroll hosts itself. 144 145 Place the exports in the exchange inventory host directory: 146 147 ``` 148 inventories/host_vars/$HOST/monitoring-client.yml 149 inventories/host_vars/$HOST/monitoring-client-secrets.yml 150 ``` 151 152 The directory uses this repository's inventory hostname, such as `spec`. 153 The identity inside the export remains the identity enrolled on Sentol; 154 it need not equal that inventory alias. Use the complete exports without 155 renaming their fields or combining them with other host variables. 156 157 The proxy bind address defaults to `127.0.0.1` when omitted from the bundle. 158 Set `monitoring_client.node_exporter.proxy_bind_address` to `"*"` (quoted 159 in YAML) to listen on all IPv4 and IPv6 interfaces. This requires IPv6 160 support on the node. Alternatively, use a literal IPv4 or IPv6 address 161 assigned to the exchange node and reachable from the monitoring server. 162 Hostnames and bare wildcard addresses (`0.0.0.0` and `::`) are rejected; 163 use `"*"` for both address families. With `"*"`, the proxy and loopback 164 backend must use different ports (normally 9100 and 9101). The proxy 165 still requires mTLS, and the backend remains loopback-only. Keep the 166 enrolled certificate identity unchanged. 167 168 Commit both files, encrypting the entire secrets file with Ansible Vault 169 before staging it: 170 171 ``` 172 $ chmod 0600 inventories/host_vars/$HOST/monitoring-client-secrets.yml 173 $ ansible-vault encrypt inventories/host_vars/$HOST/monitoring-client-secrets.yml 174 ``` 175 176 Ansible uses the existing `vault_pass.txt` configuration. The committed 177 secrets file must begin with `$ANSIBLE_VAULT;`; plaintext secrets are 178 rejected when monitoring is enabled. Do not commit the plaintext export 179 or the vault password. The Ansible controller needs Python 3 and OpenSSL 180 for bundle validation. As with other vaulted host variables, Ansible may 181 need the vault password even when monitoring management is disabled. 182 183 Set this in the host's public inventory configuration, then deploy normally: 184 185 ```yaml 186 enable_monitoring: true 187 ``` 188 189 ``` 190 $ ./deploy "$HOST" 191 ``` 192 193 The `monitoring` role installs Debian's node_exporter with a loopback-only 194 listener and systemd metrics, a dedicated `node-exporter-proxy` nginx 195 service requiring Sentol's mTLS client identity, and buffered Fluent Bit 196 JSON Lines forwarding over HTTPS/mTLS. All connection settings and certificate identities 197 come from the bundle. TLS material lives in `/etc/taler-monitoring/tls`; 198 private keys and the enclosing directory are accessible only to root. 199 The root-running Fluent Bit service reads the journal and client key. The role 200 requires Debian 13 amd64/arm64 and installs pinned Fluent Bit 5.1.2 from its signed 201 upstream APT repository. The bundle declares `logs.protocol: jsonline` and an HTTPS 202 `logs.url` ending in `/jsonline`; old RELP bundles fail preflight. 203 204 Allow Sentol to reach the exported metrics proxy address and port 205 (normally TCP 9100), and allow the node to reach the exported HTTPS log server 206 (normally TCP 2514). Firewall and routing configuration is external to 207 this role. The dedicated proxy leaves exchange webserver sites intact. 208 209 Verify the local services with: 210 211 ``` 212 $ systemctl is-active prometheus-node-exporter node-exporter-proxy fluent-bit 213 $ curl --fail http://127.0.0.1:9101/metrics >/dev/null 214 $ logger --tag external-monitoring-enrollment 'Exchange monitoring test' 215 ``` 216 217 Use the exported backend address if it differs. Follow the external-client 218 runbook to perform an authenticated scrape from Sentol, confirm anonymous 219 scrapes fail, and find the test message in VictoriaLogs using the exported identity as 220 `authenticated_host`. Fluent Bit metrics are available through the same mTLS proxy 221 at `/fluent-bit/metrics`. 222 223 Fluent Bit reads only new journal entries on first activation and resumes its saved 224 cursor on later starts. Journald uses persistent storage without changing host-specific 225 retention. `/var/lib/fluent-bit` retains the cursor and filesystem buffer, with a 226 1 GiB output queue by default (`monitoring_fluent_bit_queue_limit`). Retryable failures 227 are retried without a count limit; a full queue discards its oldest chunks. Permanent 228 HTTP errors can discard records, and VictoriaLogs can accept a batch while rejecting 229 individual malformed lines. Watch both collector drop counters and receiver errors. 230 Lost responses can cause duplicates; this is not exactly-once or unconditional 231 lossless delivery. Fluent Bit's own logs remain local to prevent feedback loops. 232 233 For renewal, retrieve both refreshed exports after `make deploy-sentol`, 234 replace the local files, encrypt the complete new secrets file before 235 staging it, commit both, and run `deploy` again. Do not reuse an old key 236 with a renewed certificate. The role validates both pairs before deployment 237 and reloads or restarts the affected services when their material changes. 238 239 `enable_monitoring` defaults to false. False skips management and leaves 240 installed monitoring running. To offboard, follow the Sentol runbook and, 241 on the exchange node, stop and disable `node-exporter-proxy` and 242 `prometheus-node-exporter` and `fluent-bit`, then remove the proxy unit 243 and `/etc/taler-monitoring` (including its private keys) and run 244 `systemctl daemon-reload`. Set the flag false and remove both inventory 245 exports. This does not revoke previously issued certificates; complete 246 the Sentol offboarding steps as well. 247 248 ### Setting up backups (TOPS-only for now) 249 250 First run: 251 252 ``` 253 $ ./extract-borg-key $DEPLOYMENT 254 ``` 255 256 The resulting SSH public key should be added to the borg-account 257 of the host storing the backup. The borg_host inventory setting selects the target 258 hostname. 259 260 Once the SSH key is deployed and the backup has been initialized 261 server-side (see admin-logs/pixel/03-borg.txt), start the daily 262 backups via: 263 264 ``` 265 $ ./start-borg-backups $DEPLOYMENT 266 ``` 267 268 This will make a backup basically everything relevant to the 269 deployment, **except** the exchange online signing keys. The 270 backup will in particular include the system configuration 271 and a full (gzip-compressed) snapshot of the database. Thus, 272 the backups should also suffice to diagnose problems. 273 274 Backups are set to retain daily snapshots of the last 7 days, 275 weekly snapshots for the last 4 weeks, and monthly snapshots 276 for the last 6 months. 277 278 279 ### Backup (right now) 280 281 To run a backup "immediately" (instead of the daily regular 282 backups), use: 283 284 ``` 285 $ ./backup $DEPLOYMENT 286 ``` 287 288 289 ### Rebooting (into a new kernel) 290 291 This should be done via the 'reboot' playbook which can 292 be invoked via the 293 294 ``` 295 $ ./reboot $DEPLOYMENT 296 ``` 297 298 script. The reboot playbook first stops all Taler services, 299 then makes a backup, and then reboots. This should help us 300 restore to another system in case the host does not come back 301 online cleanly. 302 303 304 ### Testing Locally 305 306 With podman and ansible installed locally one can run: 307 308 ``` 309 $ ./test 310 ``` 311 312 This will begin building the Containerfile in this repo, which is a Debian 313 base with systemd and a paswordless ssh server configured. Then container 314 will start, binding port 8022 to 127.0.0.1 on the host. Finally 315 the setup playbook will be run on the container via ssh. 316 317 318 ## Playbooks 319 320 ### backup 321 322 Runs a backup "right now". 323 324 ### borg-ssh-export 325 326 Exports the SSH public keys needed at the remote host for backups. 327 328 ### borg-start 329 330 Enables the borg backup. Should be run after the SSH public keys 331 exported via borg-ssh-export have been deployed on the receiving 332 host. 333 334 ### pixel-borg 335 336 Enables receiving (!) backups from pixel. Adds the public key from 337 pixel so we accept receiving borg backups from pixel. Note that 338 pixel still needs to be setup to send the backups. 339 340 ### reboot 341 342 Safely reboots the system by first stopping all Taler services, 343 then making a backup and only then actually rebooting it. 344 345 ### sanctionlist-check 346 347 Imports the latest sanction list and checks all records against 348 it. 349 350 ### setup 351 352 The main script that deploys our entire setup. 353 354 ## Roles 355 356 ### auditor 357 358 Deploys the auditor. 359 360 ### backup 361 362 Runs the backup script, making a borg backup of the database and other key parts of the system. 363 364 ### borg-ssh-export 365 366 Exports the SSH public key that must be deployed on the host that is 367 to receive the backup. 368 369 ### borg-start 370 371 Deploys the backup script and the cron job that runs it daily. 372 373 ### cert 374 375 Issues or renews a LetsEncrypt certificate with certbot and enables the 376 nginx sites that use it. Included by the roles that own a vhost, not 377 listed in a playbook directly. 378 379 ### challenger 380 381 Deploys the various challenger services for address verification. 382 383 ### common_packages 384 385 Installs the base system packages we need on all hosts. 386 Sets up Taler package repo and installs Taler packages. 387 388 ### database 389 390 Installs PostgreSQL and the dependencies needed to administer it through 391 Ansible, then ensures the database service is running. Normal deployment 392 never restores a backup. 393 394 ### database_restore 395 396 Implements the guarded fresh-cluster restore used only by `playbooks/restore.yml` 397 and `restore`. 398 399 ### devtesting 400 401 Sets up a restricted account for faking wire transfers and inspecting 402 challenger auth codes. Gated on "dangerously_enable_devtesting" and never 403 to be enabled in production. 404 405 ### exchange 406 407 Deploys the Taler exchange, then hands over to "exchange_$DEPLOYMENT_KIND" 408 for the site-specific configuration. 409 410 ### exchange_tops 411 412 Deployment-specific exchange configuration (denominations, KYC rules, fees, 413 legal documents), selected by DEPLOYMENT_KIND. 414 415 ### exchange-sanctionlist-import 416 417 Imports a new sanction list and checks all existing records against it. 418 419 ### libeufin-nexus 420 421 Deploys libeufin-nexus which connects us to the bank. 422 423 ### monitoring 424 425 Provisions external node monitoring when `enable_monitoring` is true. 426 427 ### pixel_borg 428 429 Configures the host to hosting backups *from* pixel. 430 431 ### reboot 432 433 Reboots the system. 434 435 ### stop_services 436 437 Stops all Taler-related services. Useful for emergency stop and 438 used as part of the reboot playbook. 439 440 ### webserver 441 442 Configures Nginx reverse proxy (main service, not individual subdomains). 443 444 445 ## Checks 446 447 ``` 448 $ ./contrib/test-fact-helpers.sh # the /bin helpers that generate local facts 449 ``` 450 451 Also run by the CI job in "contrib/ci/jobs/001-build". 452 453 ## Deployment safety and recovery 454 455 Normal deployment still upgrades system packages. It stops existing application 456 services, targets, timers and sockets before package work, suppresses application 457 starts through Debian's policy-rc.d mechanism, and starts applications 458 only after all configuration and database migrations finish. Schedule deployment 459 as downtime. During the system-wide upgrade, the temporary policy denies Taler 460 service actions and delegates other actions to the existing administrator policy 461 (or permits them if none existed). Application package tasks use policy_rc_d: 101. 462 The original policy is restored even on failure. PostgreSQL and nginx are started 463 as needed during provisioning. 464 Cold exchange startup may spend several minutes generating denomination keys. 465 post_deployment_http_retries defaults to 60 (with a five-second request timeout 466 and one-second retry delay); systemd unit checks retain their shorter retry limit. 467 468 A failed deployment leaves applications stopped without removing their boot 469 enablement. Inspect the failed task, correct the problem, then rerun deploy. 470 Do not reboot or manually start applications to bypass an unfinished migration. 471 Optional services whose provisioning is skipped are restored if they were active 472 when that deployment began. After a failed deployment, also inspect any optional 473 services before returning the host to service. 474 475 Setting dangerously_enable_devtesting to false revokes the managed login keys, 476 sudo access and helper programs, terminates that account's sessions and disables 477 the account. Its home-directory data is retained. Setting it true restores the 478 managed account settings and access. 479 480 The KYC attribute encryption key is preserved in a restricted secret include. 481 Do not rotate this key as a substitute for moving it: changing it without a data 482 migration would prevent decryption of existing attributes. Auditor authentication 483 is stored in a root-only nginx include. Secret tasks suppress output and diffs, 484 including when removing secrets from previously public configuration files. 485 486 Backup and restore share borg_host and borg_repo inventory defaults, retaining 487 ssh://borg@pixel.taler-systems.com/~/spec-backup for existing installations. Set 488 borg_repo in host variables to use another already provisioned repository. 489 Existing database_restore_borg_repository and database_restore_borg_host overrides 490 remain supported. No archives are moved automatically. 491 492 The backup script uses /root/.ssh/borg-config and leaves /root/.ssh/config intact. 493 A lock protects the entire backup operation. Install the updated backup script 494 between backup runs: an already running older script does not acquire this lock. If cron, backup or reboot 495 encounters another backup, it exits nonzero (script status 75) without touching 496 that run's snapshot. Retry after the existing backup finishes; a reboot playbook 497 aborts before rebooting on this failure. Pruning and compaction are skipped after 498 an unsuccessful or warning-producing archive creation. 499 500 ## Exchange HTTP latency monitoring 501 502 Sentol can opt an exchange into latency collection with 503 `monitoring_nginx_latency_enabled: true` in its external-host inventory. Its 504 exported public bundle then contains `monitoring_client.nginx_latency.enabled: 505 true`. Redistribute the bundle and deploy this repository with `enable_monitoring` 506 enabled. Missing or false capability values disable the feature; the capability 507 must be a boolean and the exchange domain must be a safe hostname. 508 509 Fluent Bit tails `/var/log/nginx/<exchange_domain>.tal` and aggregates nginx's 510 `rt` field into `nginx_request_duration_seconds` histogram buckets, sum and count. 511 The labels are `operation`, `method` and `status`. The checked-in 512 `roles/monitoring/files/exchange-latency-routes.json` allowlist is shared by nginx 513 GET selection and Fluent Bit classification; update it alongside exchange API 514 route changes. Operation names omit identifiers; `/batch-deposit` is `deposit`. 515 Unrecognized mutation routes are `other`. Malformed records increment 516 `nginx_timing_log_parse_errors_total`; oversized lines are skipped so tailing can 517 continue. Fluent Bit diagnostics report oversized lines separately. 518 519 POST, PATCH and DELETE keep their existing coverage. The exchange's nginx 520 virtual host additionally logs known API GETs, including ordinary requests to 521 endpoints that optionally long-poll. Reserve status, deposit tracking, purse 522 merge/deposit status, KYC check and KYC info GETs are included only when 523 `timeout_ms` is absent or consists entirely of zero digits. Purse requests also 524 check the documented `deposit_timeout_ms` argument conservatively. Calls with a 525 positive timeout are excluded even when they return immediately. 526 527 On these polling-capable routes, repeated timeout arguments, malformed values, 528 and queries containing percent escapes or plus signs are excluded. This may omit 529 unusual ordinary calls, but prevents encoded polling arguments entering latency 530 charts. Query strings are inspected only inside nginx and are not added to timing 531 logs. Ordinary `/keys` requests remain included when server trouble makes them 532 slow. Unknown GET routes, metrics endpoints, static downloads, HEAD and OPTIONS 533 are excluded. Other services' timing-log selection is unaffected. 534 535 Generated metrics listen on `127.0.0.1:2021/metrics` and are exposed through the 536 existing Sentol-only mTLS proxy at `/nginx/metrics`. Port 2021 must not conflict 537 with the configured node exporter or metrics proxy. The usual collector-health 538 endpoint on port 2020 and journal forwarding continue separately; timing records 539 are consumed locally and never sent to VictoriaLogs. 540 541 First enrollment starts at the end of the existing timing log. Later starts 542 resume `/var/lib/fluent-bit/exchange-timing.db`; deployment must not remove this 543 cursor. Rotation should rename the file and tell nginx to reopen its logs; 544 Fluent Bit continues watching the old descriptor for 30 seconds. Rotation while 545 the collector is stopped can lose observations: the input follows the active 546 filename and does not replay rotated archives. A dedicated logrotate rule retains 14 547 daily timing logs with delayed compression, matching Debian's standard nginx 548 policy (whose `*.log` pattern does not cover `.tal`). This rule remains installed 549 when latency metrics are disabled, since mutation timing logs are still written. 550 551 Histogram state is in memory and resets on collector restart. Prometheus retains 552 already scraped history, but can lose observations since its last scrape. Unread 553 records appear when processed after recovery, which can temporarily distort 554 rates. This is operational monitoring, not exact accounting. The Perses dashboard 555 uses counter-aware five-minute queries and defers latency alert thresholds until 556 a baseline is available. Disabling the capability removes the scrape route and 557 collector input after redeployment, while preserving the cursor for later use. 558 559 Diagnose collection locally with `curl http://127.0.0.1:2021/metrics` and 560 `journalctl -u fluent-bit`; inspect the latency dashboard's scrape and parse-error 561 panels on Sentol. Before deployment, configuration is checked with the pinned 562 Fluent Bit binary's `--dry-run` mode and nginx's `-t` mode. 563 564 ## Regression checks 565 566 `python3 contrib/tests/test_deployment_recovery.py` uses the local 567 `ansible-taler-test` image built by `./test` (or a Debian systemd image passed 568 with `--image`). It runs the real setup failure/retry flow and recovery handlers 569 against disposable service fixtures, checking lost notifications, unchanged 570 retries, check mode, validation failures and disabled monitoring. It requires 571 the `containers.podman` Ansible collection and runs without container networking 572 or package downloads. The full `./test` runner includes this regression. 573 574 `python3 contrib/tests/test_nginx_latency.py` checks the timing parser and bounded 575 route classification (requires Python Jinja2 and Lua). The `tsys-infra/monitoring` 576 Podman suite exercises this repository's real monitoring role, nginx GET 577 selection, pinned Fluent Bit histograms, mTLS scraping, rotation, cursor recovery, 578 and Perses queries across resets and idle windows. 579 580 Run python3 contrib/tests/test_backup.py for isolated concurrency, failure, 581 cleanup and archive round-trip checks (requires Borg and Python Jinja2). 582 The extended ./test also exercises real systemd shutdown, package-start 583 suppression, a pre-2.20 Ansible dependency bootstrap, secret permissions, 584 devtesting revocation, preservation of unrelated configuration, failed deployment 585 and recovery. All fixtures run in the disposable container, never an inventory 586 production host. Monitoring regressions also cover Vault-encrypted onboarding 587 bundles, preflight failures before application shutdown, authenticated metrics, 588 JSON Lines delivery, certificate renewal, idempotence, and check mode.