OPS-12: systemd haertung und db rollentrennung (F-15)
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
10 bisher ungehaertete Archive-Backup/Reconcile/Scrub-Oneshot-Vorlagen (deploy/systemd/) bekommen die etablierten Sandbox-Direktiven (NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, ggf. ReadWritePaths). Drei Vorlagen ohne Dateisystemzugriff (reconcile, retention-notify, scrub) bekommen bewusst keine ReadWritePaths, begruendet dokumentiert (S3-kompatible Storage-Abstraktion statt lokaler Pfade). Neue, getrennte Postgres-Laufzeit-Rolle (nexarch_tenant_runtime, OHNE CREATEDB) statt der bisher gemeinsam genutzten CREATEDB-Rolle fuer NEXARCH_TENANT_DSN_TEMPLATE. Provisioner.Provision() berechtigt die neue Rolle je frisch angelegter Tenant-DB (GRANT CONNECT + Schema- Rechte inkl. Default-Privileges), Tenant.DBDSN wird jetzt mit dieser Rolle statt der CREATEDB-Rolle gebaut. Neue Umgebungsvariable NEXARCH_TENANT_RUNTIME_DSN_TEMPLATE, deploy/install.sh legt die Rolle automatisch an. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c3c9034650
commit
02038866a9
14 files changed
+172
-15
No files matched your search
+1
-1
@@ -43,7 +43,7 @@ func main() {
|
||||
auditLog := audit.NewLog(registryPool)
|
||||
|
||||
registry := tenant.NewRegistry(registryPool).WithAudit(auditLog)
|
||||
provisioner := tenant.NewProvisioner(adminPool, registry, cfg.TenantDSNTemplate)
|
||||
provisioner := tenant.NewProvisioner(adminPool, registry, cfg.TenantDSNTemplate, cfg.TenantRuntimeDSNTemplate)
|
||||
tenantHandler := tenant.NewHandler(provisioner)
|
||||
onboardingService := tenant.NewOnboardingService(provisioner, cfg.TenantSchemaPath)
|
||||
onboardingHandler := tenant.NewOnboardingHandler(onboardingService)
|
||||
|
||||
+17
-1
@@ -36,6 +36,7 @@ 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_TENANT_RUNTIME_ROLE="${NEXARCH_PG_TENANT_RUNTIME_ROLE:-nexarch_tenant_runtime}"
|
||||
PG_DB="${NEXARCH_PG_REGISTRY_DB:-nexarch_registry}"
|
||||
ENV_FILE=/etc/nexarch/core.env
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -77,6 +78,19 @@ else
|
||||
PG_REGISTRY_PASSWORD=""
|
||||
fi
|
||||
|
||||
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname = '${PG_TENANT_RUNTIME_ROLE}'" | grep -q 1; then
|
||||
PG_TENANT_RUNTIME_PASSWORD="$(openssl rand -hex 24)"
|
||||
# OPS-12/F-15: separate Rolle OHNE CREATEDB fuer die laufende
|
||||
# Tenant-DB-Verbindung aller Dienste -- die CREATEDB-faehige Rolle
|
||||
# (PG_ROLE) wird nur noch intern beim Provisioning verwendet, nie mehr
|
||||
# an konsumierende Dienste weitergegeben.
|
||||
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "CREATE ROLE ${PG_TENANT_RUNTIME_ROLE} WITH LOGIN PASSWORD '${PG_TENANT_RUNTIME_PASSWORD}' NOCREATEDB;"
|
||||
echo "Rolle '${PG_TENANT_RUNTIME_ROLE}' angelegt."
|
||||
else
|
||||
echo "Rolle '${PG_TENANT_RUNTIME_ROLE}' existiert bereits — Passwort wird NICHT neu gesetzt (siehe ${ENV_FILE} fuer das vorhandene)."
|
||||
PG_TENANT_RUNTIME_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})."
|
||||
@@ -90,11 +104,12 @@ sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER ROLE ${PG_ROLE} WITH CREATEDB
|
||||
# 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
|
||||
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER ROLE ${PG_TENANT_RUNTIME_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
|
||||
if [ -z "${PG_PASSWORD}" ] || [ -z "${PG_REGISTRY_PASSWORD}" ] || [ -z "${PG_TENANT_RUNTIME_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
|
||||
@@ -128,6 +143,7 @@ if [ ! -f "$ENV_FILE" ]; then
|
||||
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_RUNTIME_DSN_TEMPLATE=postgresql://${PG_TENANT_RUNTIME_ROLE}:${PG_TENANT_RUNTIME_PASSWORD}@localhost:5432/%s?sslmode=disable
|
||||
NEXARCH_TENANT_SCHEMA_PATH=${INSTALL_DIR}/migrations/tenant
|
||||
NEXARCH_LISTEN_ADDR=127.0.0.1:8080
|
||||
NEXARCH_CORE_ADMIN_TOKEN=${CORE_ADMIN_TOKEN}
|
||||
|
||||
@@ -7,3 +7,8 @@ Type=oneshot
|
||||
User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-backup.env
|
||||
ExecStart=__INSTALL_DIR__/bin/backup-cli full
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=__BACKUP_DIR__
|
||||
@@ -7,3 +7,8 @@ Type=oneshot
|
||||
User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-backup.env
|
||||
ExecStart=__INSTALL_DIR__/bin/backup-cli incremental
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=__BACKUP_DIR__
|
||||
@@ -7,3 +7,8 @@ Type=oneshot
|
||||
User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-backup.env
|
||||
ExecStart=__INSTALL_DIR__/bin/backup-cli rotate
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=__BACKUP_DIR__
|
||||
@@ -7,3 +7,8 @@ Type=oneshot
|
||||
User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-objectbackup.env
|
||||
ExecStart=__INSTALL_DIR__/bin/objectbackup-cli backup __OBJECT_SOURCE_DIR__
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=__OBJECT_SOURCE_DIR__ __OBJECTBACKUP_REPO_DIR__
|
||||
@@ -7,3 +7,8 @@ Type=oneshot
|
||||
User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-objectbackup.env
|
||||
ExecStart=__INSTALL_DIR__/bin/objectbackup-cli check
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=__OBJECTBACKUP_REPO_DIR__
|
||||
@@ -7,3 +7,8 @@ Type=oneshot
|
||||
User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-objectbackup.env
|
||||
ExecStart=__INSTALL_DIR__/bin/objectbackup-cli rotate
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=__OBJECTBACKUP_REPO_DIR__
|
||||
@@ -8,3 +8,9 @@ User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-reconcile.env
|
||||
ExecStart=__INSTALL_DIR__/bin/reconcile-cli
|
||||
StandardOutput=journal
|
||||
# OPS-12/F-15: reine DB-/Netzwerkarbeit (S3-kompatible Storage-Abstraktion,
|
||||
# kein lokaler Dateipfad), daher keine ReadWritePaths noetig.
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
@@ -8,3 +8,9 @@ User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-retention-notify.env
|
||||
ExecStart=__INSTALL_DIR__/bin/retention-notify-job
|
||||
StandardOutput=journal
|
||||
# OPS-12/F-15: reine DB-/Netzwerkarbeit (S3-kompatible Storage-Abstraktion,
|
||||
# kein lokaler Dateipfad), daher keine ReadWritePaths noetig.
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
@@ -8,3 +8,9 @@ User=nexarch
|
||||
EnvironmentFile=/etc/nexarch/archive-scrub.env
|
||||
ExecStart=__INSTALL_DIR__/bin/scrub-cli
|
||||
StandardOutput=journal
|
||||
# OPS-12/F-15: reine DB-/Netzwerkarbeit (S3-kompatible Storage-Abstraktion,
|
||||
# kein lokaler Dateipfad), daher keine ReadWritePaths noetig.
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
@@ -15,3 +15,12 @@ EnvironmentFile=/etc/nexarch/archive-tenantbackup.env
|
||||
# Registry-Anbindung.
|
||||
ExecStart=__INSTALL_DIR__/bin/tenantbackup-cli backup ${NEXARCH_TENANTBACKUP_TENANT_DB} ${NEXARCH_TENANTBACKUP_STORAGE_ROOT}
|
||||
StandardOutput=journal
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
# OPS-12/F-15: __TENANTBACKUP_STORAGE_BASE__ ist der gemeinsame
|
||||
# Basispfad, aus dem NEXARCH_TENANTBACKUP_STORAGE_ROOT je Mandant einen
|
||||
# Unterpfad waehlt (systemd expandiert keine Env-Variablen in
|
||||
# ReadWritePaths, daher Basispfad statt Einzelwert).
|
||||
ReadWritePaths=__TENANTBACKUP_STORAGE_BASE__
|
||||
@@ -17,8 +17,13 @@ type Config struct {
|
||||
// fuer CREATE/DROP DATABASE beim Tenant-Provisioning verwendet.
|
||||
AdminDSN string
|
||||
// TenantDSNTemplate enthaelt genau ein "%s" als Platzhalter fuer den
|
||||
// Datenbanknamen einer neu provisionierten Tenant-Datenbank.
|
||||
// Datenbanknamen einer neu provisionierten Tenant-Datenbank, mit der
|
||||
// CREATEDB-faehigen Rolle (nur intern fuers Provisioning verwendet).
|
||||
TenantDSNTemplate string
|
||||
// TenantRuntimeDSNTemplate wie TenantDSNTemplate, aber mit einer
|
||||
// separaten Rolle OHNE CREATEDB (OPS-12/F-15) — wird als DBDSN an alle
|
||||
// konsumierenden Dienste ausgegeben, statt der CREATEDB-Rolle.
|
||||
TenantRuntimeDSNTemplate string
|
||||
// TenantSchemaPath zeigt auf migrations/tenant/0001_users.up.sql, mit der
|
||||
// TEN-02 eine frisch provisionierte Tenant-Datenbank initialisiert.
|
||||
TenantSchemaPath string
|
||||
@@ -40,6 +45,11 @@ func Load() (Config, error) {
|
||||
return Config{}, fmt.Errorf("NEXARCH_TENANT_DSN_TEMPLATE not set")
|
||||
}
|
||||
|
||||
runtimeDSNTemplate := os.Getenv("NEXARCH_TENANT_RUNTIME_DSN_TEMPLATE")
|
||||
if runtimeDSNTemplate == "" {
|
||||
return Config{}, fmt.Errorf("NEXARCH_TENANT_RUNTIME_DSN_TEMPLATE not set")
|
||||
}
|
||||
|
||||
schemaPath := os.Getenv("NEXARCH_TENANT_SCHEMA_PATH")
|
||||
if schemaPath == "" {
|
||||
return Config{}, fmt.Errorf("NEXARCH_TENANT_SCHEMA_PATH not set")
|
||||
@@ -51,10 +61,11 @@ func Load() (Config, error) {
|
||||
}
|
||||
|
||||
return Config{
|
||||
ListenAddr: addr,
|
||||
RegistryDSN: dsn,
|
||||
AdminDSN: adminDSN,
|
||||
TenantDSNTemplate: dsnTemplate,
|
||||
TenantSchemaPath: schemaPath,
|
||||
ListenAddr: addr,
|
||||
RegistryDSN: dsn,
|
||||
AdminDSN: adminDSN,
|
||||
TenantDSNTemplate: dsnTemplate,
|
||||
TenantRuntimeDSNTemplate: runtimeDSNTemplate,
|
||||
TenantSchemaPath: schemaPath,
|
||||
}, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -20,16 +21,48 @@ var ErrTenantExists = errors.New("tenant: slug bereits vergeben")
|
||||
type Provisioner struct {
|
||||
// adminPool ist mit der Wartungsdatenbank (z. B. "postgres") verbunden
|
||||
// und wird ausschliesslich fuer CREATE/DROP DATABASE verwendet, da diese
|
||||
// Befehle in PostgreSQL nicht in einer Transaktion laufen koennen.
|
||||
// Befehle in PostgreSQL nicht in einer Transaktion laufen koennen. Die
|
||||
// dahinterliegende Rolle hat CREATEDB.
|
||||
adminPool *pgxpool.Pool
|
||||
registry *Registry
|
||||
// dsnTemplate enthaelt genau ein "%s" als Platzhalter fuer den
|
||||
// Datenbanknamen, z. B. "postgresql://user:pass@host:5432/%s?sslmode=disable".
|
||||
dsnTemplate string
|
||||
// adminDSNTemplate nutzt dieselbe CREATEDB-Rolle wie adminPool, genau ein
|
||||
// "%s" fuer den Datenbanknamen. Dient NUR dazu, sich einmalig mit der neu
|
||||
// angelegten Tenant-Datenbank zu verbinden, um dort Schema-Rechte an die
|
||||
// Laufzeit-Rolle zu vergeben (Eigentuemer nach CREATE DATABASE ist immer
|
||||
// die verbindende Rolle, also dieselbe wie adminPool).
|
||||
adminDSNTemplate string
|
||||
// runtimeDSNTemplate nutzt eine EIGENE Rolle OHNE CREATEDB (OPS-12/F-15:
|
||||
// getrennte Rolle statt der gemeinsamen CREATEDB-Rolle fuer alle Dienste).
|
||||
// Wird als Tenant.DBDSN an alle konsumierenden Module zurueckgegeben.
|
||||
runtimeDSNTemplate string
|
||||
// runtimeRole ist der aus runtimeDSNTemplate extrahierte Rollenname, fuer
|
||||
// die GRANT-Anweisungen bei der Provisionierung.
|
||||
runtimeRole string
|
||||
}
|
||||
|
||||
func NewProvisioner(adminPool *pgxpool.Pool, registry *Registry, dsnTemplate string) *Provisioner {
|
||||
return &Provisioner{adminPool: adminPool, registry: registry, dsnTemplate: dsnTemplate}
|
||||
// NewProvisioner erwartet zwei DSN-Vorlagen mit je genau einem "%s" fuer den
|
||||
// Datenbanknamen: adminDSNTemplate mit der CREATEDB-faehigen Rolle (nur fuer
|
||||
// Provisioning verwendet), runtimeDSNTemplate mit einer separaten Rolle ohne
|
||||
// CREATEDB (wird an alle Dienste als laufende Tenant-Verbindung ausgegeben).
|
||||
func NewProvisioner(adminPool *pgxpool.Pool, registry *Registry, adminDSNTemplate, runtimeDSNTemplate string) *Provisioner {
|
||||
role := runtimeRoleFromDSNTemplate(runtimeDSNTemplate)
|
||||
return &Provisioner{
|
||||
adminPool: adminPool,
|
||||
registry: registry,
|
||||
adminDSNTemplate: adminDSNTemplate,
|
||||
runtimeDSNTemplate: runtimeDSNTemplate,
|
||||
runtimeRole: role,
|
||||
}
|
||||
}
|
||||
|
||||
// runtimeRoleFromDSNTemplate liest den Benutzernamen aus der DSN-Vorlage,
|
||||
// damit kein separater Konfigurationswert fuer den Rollennamen noetig ist.
|
||||
func runtimeRoleFromDSNTemplate(dsnTemplate string) string {
|
||||
u, err := url.Parse(fmt.Sprintf(dsnTemplate, "placeholder"))
|
||||
if err != nil || u.User == nil {
|
||||
return ""
|
||||
}
|
||||
return u.User.Username()
|
||||
}
|
||||
|
||||
// Provision legt die Tenant-Datenbank an und registriert sie. Schlaegt die
|
||||
@@ -68,11 +101,16 @@ func (p *Provisioner) Provision(ctx context.Context, slug, name string) (Tenant,
|
||||
return Tenant{}, fmt.Errorf("tenant-datenbank isolieren (revoke connect from public): %w", err)
|
||||
}
|
||||
|
||||
if err := p.grantRuntimeRole(ctx, dbName); err != nil {
|
||||
p.rollbackDatabase(ctx, dbName)
|
||||
return Tenant{}, fmt.Errorf("laufzeit-rolle berechtigen: %w", err)
|
||||
}
|
||||
|
||||
t := Tenant{
|
||||
Slug: slug,
|
||||
Name: name,
|
||||
DBName: dbName,
|
||||
DBDSN: fmt.Sprintf(p.dsnTemplate, dbName),
|
||||
DBDSN: fmt.Sprintf(p.runtimeDSNTemplate, dbName),
|
||||
Status: StatusActive,
|
||||
}
|
||||
|
||||
@@ -101,6 +139,41 @@ func (p *Provisioner) Provision(ctx context.Context, slug, name string) (Tenant,
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// grantRuntimeRole verbindet sich einmalig mit der frisch angelegten
|
||||
// Tenant-Datenbank (als Eigentuemerin, ueber adminDSNTemplate) und
|
||||
// berechtigt die separate Laufzeit-Rolle (OPS-12/F-15: ohne CREATEDB) auf
|
||||
// das public-Schema, inklusive Default-Privileges fuer Tabellen, die
|
||||
// spaeter durch Migrationen angelegt werden.
|
||||
func (p *Provisioner) grantRuntimeRole(ctx context.Context, dbName string) error {
|
||||
if p.runtimeRole == "" {
|
||||
return fmt.Errorf("laufzeit-rolle konnte nicht aus runtimeDSNTemplate ermittelt werden")
|
||||
}
|
||||
dbPool, err := pgxpool.New(ctx, fmt.Sprintf(p.adminDSNTemplate, dbName))
|
||||
if err != nil {
|
||||
return fmt.Errorf("verbindung zur neuen tenant-datenbank: %w", err)
|
||||
}
|
||||
defer dbPool.Close()
|
||||
|
||||
stmts := []string{
|
||||
fmt.Sprintf(`GRANT CONNECT ON DATABASE %q TO %s`, dbName, pgIdent(p.runtimeRole)),
|
||||
fmt.Sprintf(`GRANT ALL ON SCHEMA public TO %s`, pgIdent(p.runtimeRole)),
|
||||
fmt.Sprintf(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO %s`, pgIdent(p.runtimeRole)),
|
||||
fmt.Sprintf(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO %s`, pgIdent(p.runtimeRole)),
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := dbPool.Exec(ctx, s); err != nil {
|
||||
return fmt.Errorf("berechtigung setzen (%s): %w", s, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pgIdent quotiert einen Postgres-Bezeichner (Rollenname). Der Rollenname
|
||||
// stammt aus der eigenen Konfiguration (DSN-Vorlage), nicht aus Nutzereingabe.
|
||||
func pgIdent(name string) string {
|
||||
return `"` + name + `"`
|
||||
}
|
||||
|
||||
// rollbackDatabase entfernt eine bereits angelegte Tenant-Datenbank, wenn die
|
||||
// Registrierung fehlschlug, damit Provisioning insgesamt atomar wirkt.
|
||||
func (p *Provisioner) rollbackDatabase(ctx context.Context, dbName string) {
|
||||
|
||||
Reference in new issue
Block a user