TLS-Gate: die Node als Ansible-Rolle — Handout fĂŒr Katze & Crew

Von: Branko → OZMAI / Ansible-Katze & Crew
Stand: 1. Oktober 2026
Typ: EXTERNAL_SOURCE (Inbox-Regel §3 — keine automatische Geltung, kein Brain-Status)
Charakter: HANDOUT / ÜBERGABE. Der Soll-Zustand ist belegt (đŸ§Ș, im Container); die Rolle selbst ist ⬜ OPEN. Kein Deploy auf eine echte Node.
Quelle: Repo TLS-Gate-Mock-Pipeline (git.crumbforest.org), Commit 702afe3 (main): docs/HANDOUT_ANSIBLE.md, docker_vm/HANDBUCH.md,
docker_vm/provision/instance.sh, docker_vm/tests/{run,p3,p4,p5}.sh (52 + 43 + 54 + 31 PrĂŒfungen, am 1.10. grĂŒn), bin/TLS-doktor.bash (P6 Pool-Check, 90d6358).
Anschluss an: 2026-09-29_TLS-GATE-V3-STAND-HERKUNFT-NODE-CA-MIGRATION_BRANKO.md.

Legende: ✅ real/gebaut · đŸ§Ș gebaut, getestet, nicht live · 🛠 spezifiziert · ⬜ OPEN · ❓ UNKNOWN

Zugangsdaten, Kunden-Domains, Reseller-IDs und Inhalte der vertraulichen Anbieter-Doku stehen bewusst nicht hier. Alle Instanzen (ozm01, ozm02) und der Registrar sind Mock bzw. Fake.


Auftrag: Das, was heute docker_vm/provision/instance.sh im Container tut, wird eine Ansible-Rolle fĂŒr die echte Node.
Die Referenz steht fest und ist belegt: Der Container besteht vier Abnahme-Suiten (52 + 43 + 54 + 31 PrĂŒfungen), die Rolle soll dieselben ZustĂ€nde auf dem Zielhost erzeugen.

1. Was ihr ĂŒbernehmt (Skript-Schritt → Rollenteil)

instance.sh-Schritt Soll-Zustand Ansible
Pakete (Dockerfile) nginx, mariadb-server, php8.4-{fpm,cli,mysql,soap,mbstring,xml}, openssh-server; Standard-Pool www und Standard-Vhost aus package, command/file
Geheimnisse je Instanz DB-, DDL-, Admin-Passwort und root.key (32 Byte, Format <id>:<base64>), nur root lesbar ansible.builtin.password-Lookup bzw. Vault; nie ins Repo/Log (no_log)
Benutzer je Instanz User+Gruppe mit fester UID (ozm01 2001, ozm02 2002) user, group
Verzeichnisse /home/<n>/{app,bin,state,logs}; /home/<n> 0710 <n>:www-data; state 0700 <n>; root.key 0400 <n>; app/, bin/ gehören root file
Code native_php → app/, native_bash_cli → bin/ (root-eigen, nicht vom Web-User schreibbar) synchronize/copy
SSH AllowGroups ssh-instances, nur SchlĂŒssel, kein root, keine Tunnel; authorized_keys gehört root lineinfile/Template (conf/sshd-tlsgate.conf), authorized_key mit root-Besitz
Datenbank je Instanz Schema <n>, User <n>_ddl (alles auf das Schema, nur fĂŒr migrate) und <n>_app (nur SELECT, INSERT, UPDATE, DELETE) community.mysql.*
Pool + Vhost aus provision/templates/pool.conf.tpl, vhost.conf.tpl template + Handler (reload)
App user_cli.php migrate als <n>_ddl, Gruppe + Admin anlegen, Alltag nur als <n>_app command als Instanz-User, mit creates/StatusprĂŒfung
SelbstprĂŒfung instance.sh endet mit PrĂŒfungen assert/uri/command

2. Abnahme: wann ist die Rolle fertig

  1. Idempotenz: zweiter Lauf = changed=0 (Container: „keine Aenderung (Sollstand erreicht)“).
  2. Doktor grĂŒn, als root auf der Node, mit der Umgebung des Pools:
    bash WEB_USER=<instanz> TLS_GATE_STATE_DIR=/home/<instanz>/state TLSGATE_ROOT_KEY_FILE=/home/<instanz>/state/root.key \ TLSGATE_DB_DRIVER=mysql TLSGATE_DB_NAME=<instanz> TLSGATE_DB_USER=<instanz>_app TLSGATE_DB_PASS=
 \ /home/<instanz>/app/bin/TLS-doktor.bash
    Erwartet: Exit 0, „Alles gruen“. Er prĂŒft Erweiterungen, Schema, root.key, state/ und den php-fpm-Pool (disable_functions, clear_env, open_basedir, display_errors).
  3. Gegenproben bestehen (das ist der eigentliche Beweis): Die Suiten verĂ€ndern je eine Kleinigkeit und erwarten, dass etwas bricht — root.key 0644, ein Zeichen im DB-Passwort, popen aus disable_functions, ein Zeichen im ssh-SchlĂŒssel, Besitz von state/. SinngemĂ€ĂŸ als Ansible-Verify-Playbook (--check zeigt Drift) ĂŒbernehmen.
  4. ZugĂ€nge: ssh nur mit SchlĂŒssel (falscher SchlĂŒssel, root und fremde Instanz → Permission denied); Web-Login mit Passwort + TOTP; src/, bin/, state/, .git per Web nicht ausgeliefert.

Die Suiten selbst (Repo: docker_vm/tests/*.sh) laufen per docker exec und sind nicht direkt auf einer Node nutzbar; sie sind die Spezifikation, kein Werkzeug fĂŒr den Zielhost.

3. Was auf der Node anders ist als im Container (nicht blind kopieren)

  • TLS: Im Container keines. Auf der Node: Zertifikat und Vhost auf 443; dann TLSGATE_COOKIE_SECURE entfernen (Vorlage setzt 0 nur fĂŒr die Testinstanz).
  • Registrar: Im Container nur der Fake (soap.fake.test). Auf der Node echter Registrar ĂŒber cred add/cred test; Fake-Pool/-Vhost/-CA nicht ausrollen.
  • Zugangsdaten im Pool: Die Vorlage legt das DB-Passwort als env[...] in die Pool-Datei (root-lesbar). FĂŒr den Betrieb besser LoadCredential/Secret-Mount — offen, bitte als Entscheidung festhalten.
  • systemd, cgroup, Ports 18080/2222: Container-Eigenheiten. Auf der Node regulĂ€re Ports und systemctl.
  • Ein MariaDB, ein Schema je Instanz. Kein gemeinsames Mandantenschema; jede Instanz hat ihren eigenen root.key (die Secrets sind daran gebunden).

4. Fallen (alle im Container schon aufgetreten)

  • <n>_ddl nie in den Pool. Der Web-Prozess bekommt nur <n>_app.
  • authorized_keys und app/ gehören root. Sonst kann die Instanz ihre SchlĂŒssel bzw. ihren Code Ă€ndern.
  • systemd-user-sessions muss laufen, sonst bleibt /run/nologin stehen und jeder ssh-Login scheitert (PAM „Access denied“). Im Container Drop-in ssh-user-sessions.conf.
  • mbstring ist Pflicht (ohne: HTTP 503 mit Paketnamen); pdo_mysql bei Treiber mysql.
  • IP-Rate-Limit der App: 10 Fehlversuche in 15 min sperren die IP. Verify-LĂ€ufe, die absichtlich falsche Passwörter senden, setzen vorher die ZĂ€hler zurĂŒck oder laufen von einer anderen IP.
  • Neues Image/Host = neue ssh-HostschlĂŒssel → ssh-keygen -R.
  • dashboard.sh per ssh: bash -lc dashboard.sh (nur die Login-Shell hat den PATH), ssh -n verhindert HĂ€nger.

5. Nicht Teil des Auftrags

Rust, Node-CA, TLS-Terminierung/ACME, ein gemeinsames Mandantenschema, Host-Doktoren (Backbone/Strato/Debian — siehe HANDBUCH §10). Offen aus dem Doktor: er prĂŒft nur den Pool des WEB_USER, nicht, ob ein anderer Pool im selben php-fpm schwĂ€cher ist.

6. Reihenfolge-Vorschlag

  1. Rolle tlsgate_node (Pakete, sshd, MariaDB-Grundlage, nginx/php-fpm) — Verify: Doktor-Abschnitte Erweiterungen + Pool.
  2. Rolle tlsgate_instance (Parameter name, uid, ssh_pubkey, mode: fake|real) — Verify: Idempotenz, ssh-/Web-Gegenproben.
  3. Erst dann: zweite Instanz (ozm02) und die Isolationsproben (ozm01 kann ozm02 nicht lesen — Datei, Socket, Schema).

Tipp: ansible-good-practices und ansible-zen auf die fertige Rolle laufen lassen, bevor sie auf die Node geht.