Files
nexarch/deploy/install.sh
T
patrickandClaude Sonnet 5 4fc361b462
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 41s
TEN-09: registry-h-rtung-zugriffsschutz
Haertet die Control-Plane-Registry als eigene Schutzzone:

- Dedizierte Postgres-Rolle nexarch_registry (ohne CREATEDB) fuer
  NEXARCH_REGISTRY_DSN, getrennt von der Provisioning-Rolle nexarch_core
  (deploy/install.sh). Neu angelegte Tenant-Datenbanken entziehen PUBLIC
  explizit das CONNECT-Recht (internal/tenant/provisioner.go), damit die
  Registry-Rolle unter keinen Umstaenden Zugriff auf eine Tenant-Datenbank
  erhaelt (Akzeptanzkriterium 1).
- Auditierte Zugriffsmethoden (*AsActor) fuer Superadmin-Konten
  (internal/user.SuperadminStore) und Verbindungsinformationen
  (internal/tenant.Registry) — jeder lesende/schreibende Zugriff erzeugt
  einen Audit-Event mit Akteur und Zeitstempel, verdrahtet in
  internal/tenantadmin (TEN-05-Konsole) und cmd/core (Akzeptanzkriterium 2).
- Betriebsanleitung docs/TEN-09-REGISTRY-BETRIEB.md dokumentiert
  Rollentrennung und den auf den Core-Dienst beschraenkten Netzwerkzugriff
  (pg_hba.conf, listen_addresses, Firewall-Default-Policy), abgesichert
  durch internal/opsdocs/registry_hardening_test.go (Akzeptanzkriterium 3).

Automatisierte Tests je Akzeptanzkriterium: internal/tenant/isolation_test.go
(negativer Zugriffstest), internal/audit/wiring_test.go
(TestWiring_SuperadminReadIsAudited, TestWiring_RegistryConnectionInfoReadIsAudited),
internal/opsdocs/registry_hardening_test.go.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbP
2026-09-17 01:37:40 +02:00

192 lines
9.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# NEXARCH Core – Installer/Deployer fuer eine Debian/systemd-Zielumgebung.
#
# Baut alle Core-Binaries (cmd/core + die *-devserver), legt Postgres-Rolle
# und Registry-Datenbank an (falls nicht vorhanden), wendet die
# Registry-Migrationen an, generiert Secrets (falls nicht vorhanden) und
# installiert+startet jeden Dienst als eigenen systemd-Service.
#
# Idempotent: mehrfacher Aufruf ist sicher — vorhandene Rolle/DB/Secrets
# werden nicht ueberschrieben, Migrationen nur angewendet, wenn die
# Registry-DB noch leer ist (siehe Abschnitt "Migrationen" unten).
#
# Aufruf (als root): ./deploy/install.sh
#
# Ueberschreibbar per Umgebungsvariable:
# NEXARCH_INSTALL_DIR (Default: /opt/nexarch-core)
# NEXARCH_PG_ROLE (Default: nexarch_core)
# NEXARCH_PG_REGISTRY_ROLE (Default: nexarch_registry)
# NEXARCH_PG_REGISTRY_DB (Default: nexarch_registry)
# NEXARCH_BRANCH (Default: aktueller Branch dieses Checkouts)
#
# TEN-09 (Registry-Haertung): es gibt zwei getrennte Postgres-Rollen statt
# einer gemeinsamen Superuser-aehnlichen Rolle (der Keycloak-Fehler, den
# das Ticket ausdruecklich vermeiden soll):
# - NEXARCH_PG_ROLE (nexarch_core): hat CREATEDB, wird NUR fuer
# NEXARCH_ADMIN_DSN/NEXARCH_TENANT_DSN_TEMPLATE genutzt (Tenant-DB-
# Provisioning/Zugriff, TEN-01). Diese Rolle wird NIEMALS fuer die
# Registry-Verbindung selbst verwendet.
# - NEXARCH_PG_REGISTRY_ROLE (nexarch_registry): Eigentuemerin der
# Registry-Datenbank, OHNE CREATEDB, wird fuer NEXARCH_REGISTRY_DSN
# genutzt. Da neu angelegte Tenant-Datenbanken PUBLIC das CONNECT-Recht
# explizit entziehen (siehe internal/tenant/provisioner.go), hat diese
# Rolle keinerlei Zugriff auf Tenant-Datenbanken (Akzeptanzkriterium 1).
set -euo pipefail
INSTALL_DIR="${NEXARCH_INSTALL_DIR:-/opt/nexarch-core}"
PG_ROLE="${NEXARCH_PG_ROLE:-nexarch_core}"
PG_REGISTRY_ROLE="${NEXARCH_PG_REGISTRY_ROLE:-nexarch_registry}"
PG_DB="${NEXARCH_PG_REGISTRY_DB:-nexarch_registry}"
ENV_FILE=/etc/nexarch/core.env
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
if [ "$(id -u)" -ne 0 ]; then
echo "Bitte als root ausfuehren (legt Systembenutzer, systemd-Units und Postgres-Rolle an)." >&2
exit 1
fi
echo "== 1. Systembenutzer =="
if ! id nexarch >/dev/null 2>&1; then
useradd --system --no-create-home --shell /usr/sbin/nologin nexarch
echo "Benutzer 'nexarch' angelegt."
else
echo "Benutzer 'nexarch' existiert bereits."
fi
echo "== 2. Postgres-Rollen und Registry-Datenbank =="
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname = '${PG_ROLE}'" | grep -q 1; then
PG_PASSWORD="$(openssl rand -hex 24)"
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "CREATE ROLE ${PG_ROLE} WITH LOGIN PASSWORD '${PG_PASSWORD}';"
echo "Rolle '${PG_ROLE}' angelegt."
else
echo "Rolle '${PG_ROLE}' existiert bereits — Passwort wird NICHT neu gesetzt (siehe ${ENV_FILE} fuer das vorhandene)."
PG_PASSWORD=""
fi
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname = '${PG_REGISTRY_ROLE}'" | grep -q 1; then
PG_REGISTRY_PASSWORD="$(openssl rand -hex 24)"
# Bewusst OHNE CREATEDB — diese Rolle darf ausschliesslich die Registry-DB
# nutzen, nie eine Tenant-Datenbank anlegen oder verwalten (Akzeptanz-
# kriterium 1 / "bekannte Fehler vermeiden": keine gemeinsame Rolle mit
# Tenant-Datenbankzugriff).
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "CREATE ROLE ${PG_REGISTRY_ROLE} WITH LOGIN PASSWORD '${PG_REGISTRY_PASSWORD}' NOCREATEDB;"
echo "Rolle '${PG_REGISTRY_ROLE}' angelegt."
else
echo "Rolle '${PG_REGISTRY_ROLE}' existiert bereits — Passwort wird NICHT neu gesetzt (siehe ${ENV_FILE} fuer das vorhandene)."
PG_REGISTRY_PASSWORD=""
fi
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname = '${PG_DB}'" | grep -q 1; then
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "CREATE DATABASE ${PG_DB} OWNER ${PG_REGISTRY_ROLE};"
echo "Datenbank '${PG_DB}' angelegt (Eigentuemerin: ${PG_REGISTRY_ROLE})."
else
echo "Datenbank '${PG_DB}' existiert bereits."
fi
# Fuer Tenant-Provisioning (CREATE DATABASE pro Mandant, Modell C) braucht
# NUR die Admin-/Provisioning-Rolle CREATEDB-Recht — NICHT die Registry-Rolle.
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER ROLE ${PG_ROLE} WITH CREATEDB;" >/dev/null
# Verteidigungslinie 2: selbst falls die Registry-Rolle irgendwann doch
# CONNECT-Rechte auf einer Tenant-Datenbank erhalten sollte, bleibt sie ohne
# CREATEDB unfaehig, selbst Tenant-Datenbanken anzulegen/zu uebernehmen.
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER ROLE ${PG_REGISTRY_ROLE} WITH NOCREATEDB;" >/dev/null
echo "== 3. Secrets/Konfiguration =="
mkdir -p /etc/nexarch
if [ ! -f "$ENV_FILE" ]; then
if [ -z "${PG_PASSWORD}" ] || [ -z "${PG_REGISTRY_PASSWORD}" ]; then
echo "FEHLER: mindestens eine Rolle existierte bereits, aber ${ENV_FILE} fehlt — Passwort kann nicht rekonstruiert werden." >&2
echo "Entweder ${ENV_FILE} von Hand mit den bekannten Passwoertern anlegen, oder die Rollen droppen und erneut installieren." >&2
exit 1
fi
AUDIT_ADMIN_TOKEN="$(openssl rand -hex 32)"
# Ed25519-Schluesselpaar fuer die Lizenzsignatur (LIC-01/LIC-04):
# licadmin-devserver prueft Lizenzen nur mit dem OEFFENTLICHEN Schluessel,
# der private Schluessel wird NICHT deployt, sondern separat fuer das
# (noch zu bauende) offline Lizenz-Ausstellungswerkzeug aufbewahrt.
KEYPAIR_DIR=/etc/nexarch/license-signing
mkdir -p "$KEYPAIR_DIR"
chmod 700 "$KEYPAIR_DIR"
if [ ! -f "$KEYPAIR_DIR/private.b64" ]; then
go run "$SCRIPT_DIR/gen-ed25519-keypair.go" > "$KEYPAIR_DIR/keypair.txt"
grep '^PRIVATE=' "$KEYPAIR_DIR/keypair.txt" | cut -d= -f2 > "$KEYPAIR_DIR/private.b64"
grep '^PUBLIC=' "$KEYPAIR_DIR/keypair.txt" | cut -d= -f2 > "$KEYPAIR_DIR/public.b64"
chmod 600 "$KEYPAIR_DIR"/*.b64 "$KEYPAIR_DIR/keypair.txt"
rm -f "$KEYPAIR_DIR/keypair.txt"
fi
LICENSE_PUBLIC_KEY="$(cat "$KEYPAIR_DIR/public.b64")"
cat > "$ENV_FILE" <<EOF
# Automatisch generiert von deploy/install.sh am $(date -Iseconds).
# NICHT ins Repository committen — enthaelt Zugangsdaten/Secrets.
NEXARCH_REGISTRY_DSN=postgresql://${PG_REGISTRY_ROLE}:${PG_REGISTRY_PASSWORD}@localhost:5432/${PG_DB}?sslmode=disable
NEXARCH_ADMIN_DSN=postgresql://${PG_ROLE}:${PG_PASSWORD}@localhost:5432/postgres?sslmode=disable
NEXARCH_TENANT_DSN_TEMPLATE=postgresql://${PG_ROLE}:${PG_PASSWORD}@localhost:5432/%s?sslmode=disable
NEXARCH_TENANT_SCHEMA_PATH=${INSTALL_DIR}/migrations/tenant
NEXARCH_LISTEN_ADDR=:8080
NEXARCH_AUDIT_ADMIN_TOKEN=${AUDIT_ADMIN_TOKEN}
NEXARCH_LICENSE_PUBLIC_KEY=${LICENSE_PUBLIC_KEY}
EOF
chmod 600 "$ENV_FILE"
chown nexarch:nexarch "$ENV_FILE"
echo "${ENV_FILE} angelegt (Secrets generiert)."
else
echo "${ENV_FILE} existiert bereits — wird nicht ueberschrieben."
fi
echo "== 4. Build =="
mkdir -p "$INSTALL_DIR/bin"
cd "$REPO_ROOT"
for svc in core auditlog-devserver licadmin-devserver metrics-devserver secheaders-devserver statuspage-devserver tenantadmin-devserver; do
go build -o "$INSTALL_DIR/bin/${svc}" "./cmd/${svc}"
echo "gebaut: ${svc}"
done
cp -r migrations "$INSTALL_DIR/migrations"
chown -R nexarch:nexarch "$INSTALL_DIR"
echo "== 5. Registry-Migrationen =="
# shellcheck disable=SC1090
source "$ENV_FILE"
ALREADY_MIGRATED=$(sudo -u postgres psql -tAc "SELECT to_regclass('public.tenants') IS NOT NULL" -d "${PG_DB}")
if [ "$ALREADY_MIGRATED" = "t" ]; then
echo "Registry-Datenbank hat bereits Tabellen — Migrationen werden uebersprungen (nicht idempotent, siehe Skript-Kommentar)."
else
for f in $(find "$INSTALL_DIR/migrations" -maxdepth 1 -name '*.up.sql' | sort); do
sudo -u postgres psql -v ON_ERROR_STOP=1 -d "${PG_DB}" -f "$f" >/dev/null
echo "angewendet: $(basename "$f")"
done
fi
# Migrationen laufen als 'postgres' (fuer CREATE EXTENSION etc.) — die von
# 'postgres' angelegten Tabellen gehoeren deshalb NICHT automatisch der
# Registry-Rolle. Ohne diese expliziten GRANTs (inkl. ALTER DEFAULT
# PRIVILEGES fuer kuenftige Migrationen) koennte NEXARCH_REGISTRY_DSN gar
# nicht auf die Registry-Tabellen zugreifen.
sudo -u postgres psql -v ON_ERROR_STOP=1 -d "${PG_DB}" -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${PG_REGISTRY_ROLE};" >/dev/null
sudo -u postgres psql -v ON_ERROR_STOP=1 -d "${PG_DB}" -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ${PG_REGISTRY_ROLE};" >/dev/null
sudo -u postgres psql -v ON_ERROR_STOP=1 -d "${PG_DB}" -c "ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO ${PG_REGISTRY_ROLE};" >/dev/null
sudo -u postgres psql -v ON_ERROR_STOP=1 -d "${PG_DB}" -c "ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO ${PG_REGISTRY_ROLE};" >/dev/null
echo "== 6. systemd-Units =="
for tmpl in "$SCRIPT_DIR"/systemd/*.service.tmpl; do
name="$(basename "$tmpl" .service.tmpl)"
sed "s#__INSTALL_DIR__#${INSTALL_DIR}#g" "$tmpl" > "/etc/systemd/system/${name}.service"
done
systemctl daemon-reload
for tmpl in "$SCRIPT_DIR"/systemd/*.service.tmpl; do
name="$(basename "$tmpl" .service.tmpl)"
systemctl enable --now "${name}.service"
done
echo "== Fertig =="
echo "Status pruefen mit: systemctl status 'nexarch-*'"
echo "Health-Checks:"
echo " curl -s http://localhost:8080/healthz"
echo " curl -s http://localhost:8081/healthz # licadmin"
echo " curl -s http://localhost:8082/healthz # tenantadmin"
echo " curl -s http://localhost:8083/healthz # auditlog"
echo " curl -s http://localhost:8084/healthz # statuspage"
echo " curl -s http://localhost:8085/healthz # metrics"
echo " curl -s http://localhost:8086/unprotected # secheaders (kein /healthz)"