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.