Files
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

73 lines
2.3 KiB
Go

package user
import (
"encoding/json"
"errors"
"net/http"
)
// Handler stellt die CRUD-API fuer Benutzerkonten bereit (IAM-01-Auftrag).
// Auth/Sessions (IAM-02) und Rollen (RBAC-01) sind ausdruecklich nicht Teil
// dieser Kachel und daher hier noch nicht angebunden.
type Handler struct {
users *TenantUserStore
superadmins *SuperadminStore
}
func NewHandler(users *TenantUserStore, superadmins *SuperadminStore) *Handler {
return &Handler{users: users, superadmins: superadmins}
}
type createUserRequest struct {
Email string `json:"email"`
Name string `json:"name"`
}
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req createUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "ungueltige Anfrage", http.StatusBadRequest)
return
}
u, err := h.users.Create(r.Context(), req.Email, req.Name)
writeUserResult(w, u, err)
}
// CreateSuperadmin legt ein mandantenuebergreifendes Superadmin-Konto an —
// bewusst ein eigener Endpunkt statt eines Tenant-Parameters mit Null-Wert.
func (h *Handler) CreateSuperadmin(w http.ResponseWriter, r *http.Request) {
var req createUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "ungueltige Anfrage", http.StatusBadRequest)
return
}
// TEN-09 Akzeptanzkriterium 2: Anlage eines Superadmin-Kontos ist ein
// schreibender Zugriff auf ein Superadmin-Konto und wird auditiert.
// Echte Aufrufer-Identitaet (IAM-02-Session) ist an diesem vorlaeufigen,
// noch unauthentifizierten Bootstrap-Endpunkt nicht verfuegbar (siehe
// Paketkommentar) — "system" folgt derselben Konvention wie
// tenant.Registry.transition fuer Vorgaenge ohne bekannten Akteur.
u, err := h.superadmins.CreateAsActor(r.Context(), "system", req.Email, req.Name)
writeUserResult(w, u, err)
}
func writeUserResult(w http.ResponseWriter, u User, err error) {
if err != nil {
switch {
case errors.Is(err, ErrInvalidEmail), errors.Is(err, ErrEmailTaken):
http.Error(w, err.Error(), http.StatusBadRequest)
case errors.Is(err, ErrNotFound):
http.Error(w, err.Error(), http.StatusNotFound)
default:
http.Error(w, "benutzer konnte nicht verarbeitet werden", http.StatusInternalServerError)
}
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(u)
}