Deploy — Zielhosts

Für den Zielhost gilt immer dieselbe Regel: DocumentRoot/root = public/. Alles andere
(src/, bin/, database/, state/) ist per Verzeichnisstruktur nicht erreichbar.
Der Rest hängt vom Betriebssystem ab (Pfade, Benutzer, fpm-Socket, SELinux).

Vorlage Webserver
apache-vhost.conf.example Apache 2.4 + PHP-FPM (proxy_fcgi)
nginx.conf.example nginx + PHP-FPM

Ablauf (jedes OS)

# 1. Code
git clone … /srv/tlsgate && cd /srv/tlsgate && git checkout v2.0.0

# 2. Laufzeitdaten ausserhalb des Checkouts, nur fuer den Web-User (<WEBUSER> siehe Tabelle)
install -d -o <WEBUSER> -g <WEBUSER> -m 0700 /var/lib/tlsgate

# 3. Wurzelschluessel getrennt von der DB (docs/SECURITY.md 1c, docs/DESIGN_TENANTS.md §2)
#    Format: "<key_id>:<base64 von 32 Byte>". TOTP-Secrets (und spaeter SOAP-Zugaenge) werden daraus abgeleitet.
install -d -m 0750 -g <WEBUSER> /etc/tlsgate
( umask 0277; php -r 'echo "1:", base64_encode(random_bytes(32)), "\n";' > /etc/tlsgate/root.key )
chown root:<WEBUSER> /etc/tlsgate/root.key && chmod 0640 /etc/tlsgate/root.key

# 4. DB anlegen — als root/sudo (der CLI-Guard verlangt euid 0 oder dass dem Aufrufer alles gehört; "sudo -u <WEBUSER>" geht erst nach dem chown), mit denselben Pfaden
#    wie der Webserver. Die CLI legt die Dateien als root an, deshalb danach dem Web-User uebergeben.
export TLS_GATE_STATE_DIR=/var/lib/tlsgate TLSGATE_ROOT_KEY_FILE=/etc/tlsgate/root.key
sudo -E php bin/user_cli.php migrate            # nach jedem Update erneut: erst --dry-run, dann migrate (Sicherung liegt daneben)
sudo -E php bin/user_cli.php group add bwa "B.W.A" eigen
sudo -E php bin/user_cli.php user add admin@example.org bwa admin
sudo -E php bin/user_cli.php cred add bwa registrar-a https://…/soap.wsdl && sudo -E php bin/user_cli.php cred test bwa registrar-a
sudo chown -R <WEBUSER>:<WEBUSER> /var/lib/tlsgate      # Web-Prozess braucht Schreibrecht (SQLite -wal/-journal)

# 5. Webserver-Vorlage anpassen (server_name, Zertifikat, fpm-Socket), einbinden, testen, neu laden

# 6. Gegenprobe mit denselben Pfaden
TLS_GATE_STATE_DIR=/var/lib/tlsgate APACHE_USER=<WEBUSER> bin/TLS-doktor.bash
curl -sI https://tlsgate.example.org/login.php            # 200 + Sicherheits-Header
curl -so /dev/null -w '%{http_code}\n' --path-as-is https://tlsgate.example.org/../bin/user_cli.php  # 404/400

FPM-Pool: Umgebung für PHP

SetEnv (Apache) bzw. fastcgi_param (nginx) reichen für Pfade. SOAP-Zugangsdaten stehen weder hier noch im Pool, sondern
verschlüsselt in der DB (php bin/user_cli.php cred add <gruppe> <label> <wsdl>, danach cred test). Im Pool bleiben nur Pfade:

; <pool.d>/tlsgate.conf
[tlsgate]
user  = <WEBUSER>
group = <WEBUSER>
listen = <SOCKET>
listen.owner = <WEBUSER>
listen.group = <WEBUSER>
pm = ondemand
pm.max_children = 5
clear_env = yes
env[TLS_GATE_STATE_DIR] = /var/lib/tlsgate
env[TLSGATE_ROOT_KEY_FILE] = /etc/tlsgate/root.key
php_admin_value[open_basedir]      = /srv/tlsgate:/var/lib/tlsgate:/etc/tlsgate:/tmp
php_admin_value[disable_functions] = exec,shell_exec,system,passthru,proc_open,popen
php_admin_flag[expose_php]         = off

Hinweis zu open_basedir: Das Web braucht /srv/tlsgate (wegen src/, bootstrap.php und
resources/mock/), den State-Pfad und den Key-Pfad. disable_functions betrifft nur den Web-Pool.
bin/dashboard.sh und die CLIs laufen mit der CLI-php.ini.

Pro Betriebssystem

Debian / Ubuntu RHEL / Alma / Rocky Alpine FreeBSD
Pakete php-fpm php-sqlite3 php-soap (sodium ist im Kern) php-fpm php-pdo php-soap php-sodium php83-fpm php83-pdo_sqlite php83-sodium php83-soap php83-openssl php83 php83-pdo_sqlite php83-sodium php83-soap
<WEBUSER> www-data Apache: apache · nginx: nginx nginx (bzw. apache) www
<SOCKET> /run/php/php8.3-fpm.sock /run/php-fpm/www.sock /run/php-fpm83/php-fpm.sock (im Pool setzen) /var/run/php-fpm.sock (im Pool setzen, Standard ist TCP 127.0.0.1:9000)
FPM-Pool-Dir /etc/php/8.3/fpm/pool.d/ /etc/php-fpm.d/ /etc/php83/php-fpm.d/ /usr/local/etc/php-fpm.d/
Apache-vhost /etc/apache2/sites-available/ + a2ensite, a2enmod proxy_fcgi ssl headers /etc/httpd/conf.d/ /etc/apache2/conf.d/ /usr/local/etc/apache24/Includes/
nginx-Site /etc/nginx/sites-available/ + Symlink /etc/nginx/conf.d/ /etc/nginx/http.d/ /usr/local/etc/nginx/ (include)
Log-Makro ${APACHE_LOG_DIR} gibt es kein ${APACHE_LOG_DIR} → logs/tlsgate_error.log wie RHEL /var/log/httpd-tlsgate_error.log
Besonderheit — SELinux, siehe unten musl: PASSWORD_ARGON2ID prüfen (Doktor) Pfade unter /usr/local; bin/*.sh brauchen bash (pkg)

PHP-Versionen in der Tabelle sind Beispiele. Mindestens PHP 8.1 wird gebraucht (composer.json). Der
Doktor zeigt, ob pdo_sqlite, sodium und Argon2id in der FPM-Version vorhanden sind. CLI und FPM
können unterschiedlich gebaut sein.

SELinux (RHEL-Familie)

semanage fcontext -a -t httpd_sys_content_t    '/srv/tlsgate(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/lib/tlsgate(/.*)?'
semanage fcontext -a -t httpd_sys_content_t    '/etc/tlsgate(/.*)?'
restorecon -Rv /srv/tlsgate /var/lib/tlsgate /etc/tlsgate
setsebool -P httpd_can_network_connect on      # ausgehender SOAP-Call zum Partnergate

Ohne httpd_can_network_connect fällt die App still auf den Mock zurück. Die Oberfläche zeigt dann
die Quelle „Mock-Fallback“, obwohl die Zugangsdaten stimmen.

Hinter einem Reverse-Proxy

Die IP-Sperre nutzt REMOTE_ADDR, X-Forwarded-For wertet sie nicht aus. Deshalb muss die echte
Client-IP am Webserver ankommen: bei nginx über set_real_ip_from + real_ip_header, bei Apache über
mod_remoteip. Sonst sperrt ein einziger Angreifer den Proxy und damit alle Benutzer.