From bc46c3e32052cf026cd0449d7b7d5c2e236f6d06 Mon Sep 17 00:00:00 2001 From: sysops Date: Sun, 30 Aug 2026 21:24:35 +0200 Subject: [PATCH] RBAC-07: rechte-administrations-dienst-starten - cmd/rbac-admin-api: startet internal/rbac.Handler (RBAC-05), hinter auth.RequireAuth mit demselben JWT-Secret wie IAM-16 (account-api) - reines Wiring, kein Diff an internal/rbac/ (verifiziert) - real deployed auf 131, cross-service-Session real bewiesen: Login gegen account-api (Port 8099), dasselbe Session-Cookie gegen rbac-admin-api (Port 8100) verwendet -> echte Rollenliste, ohne Cookie -> 401 - Migrationen (role_assignments/groups) real auf tenant_acme angewendet, reale Grant-Luecke behoben und verifiziert - Bootstrap-Erkenntnis dokumentiert: ListRoles selbst verlangt bereits eine Rolle mit PermManageUsers, Testnutzer daher mit direkter DB-Bootstrap-Rolle angelegt (wie Ersteinrichtung) Pruefungen siehe docs/RBAC-07-PRUEFPROTOKOLL.md --- cmd/rbac-admin-api/main.go | 66 ++++++++++++++++ .../nexarch-rbac-admin-api.service.tmpl | 14 ++++ docs/RBAC-07-PRUEFPROTOKOLL.md | 75 +++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 cmd/rbac-admin-api/main.go create mode 100644 deploy/systemd/nexarch-rbac-admin-api.service.tmpl create mode 100644 docs/RBAC-07-PRUEFPROTOKOLL.md diff --git a/cmd/rbac-admin-api/main.go b/cmd/rbac-admin-api/main.go new file mode 100644 index 0000000..fddcc69 --- /dev/null +++ b/cmd/rbac-admin-api/main.go @@ -0,0 +1,66 @@ +// rbac-admin-api ist der Aufrufpunkt fuer RBAC-07: startet den bereits +// fertigen RBAC-05-Handler (internal/rbac) als eigenstaendigen HTTP-Dienst, +// hinter derselben Session-Auth wie IAM-16 (account-api) — GLEICHER +// JWT-Secret, damit eine bei account-api ausgestellte Session hier auch +// gueltig ist. REINES WIRING — keine Aenderung an internal/rbac/. +package main + +import ( + "context" + "log" + "net/http" + "os" + + "github.com/jackc/pgx/v5/pgxpool" + + "gitea.perlbach24.de/scripte/nexarch/internal/auth" + "gitea.perlbach24.de/scripte/nexarch/internal/rbac" +) + +func requireEnv(name string) string { + v := os.Getenv(name) + if v == "" { + log.Fatalf("%s muss gesetzt sein", name) + } + return v +} + +func main() { + dsn := requireEnv("NEXARCH_RBACADMIN_TENANT_DSN") + // MUSS identisch mit NEXARCH_ACCOUNT_JWT_SECRET (IAM-16) sein, sonst + // verwirft dieser Dienst gueltige account-api-Sessions. + jwtSecret := requireEnv("NEXARCH_RBACADMIN_JWT_SECRET") + addr := os.Getenv("NEXARCH_RBACADMIN_API_LISTEN_ADDR") + if addr == "" { + addr = "127.0.0.1:8100" + } + + ctx := context.Background() + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + log.Fatalf("datenbankverbindung: %v", err) + } + defer pool.Close() + + tokenIssuer := auth.NewTokenIssuer(jwtSecret) + roles := rbac.NewStore(pool) + groups := rbac.NewGroupStore(pool) + handler := rbac.NewHandler(roles, groups) + + mux := http.NewServeMux() + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) + + mux.HandleFunc("GET /rbac/roles", auth.RequireAuth(tokenIssuer, handler.ListRoles)) + mux.HandleFunc("POST /rbac/users/role", auth.RequireAuth(tokenIssuer, handler.AssignRole)) + mux.HandleFunc("GET /rbac/users/{id}/history", auth.RequireAuth(tokenIssuer, handler.RoleHistory)) + mux.HandleFunc("GET /rbac/groups", auth.RequireAuth(tokenIssuer, handler.ListGroups)) + mux.HandleFunc("POST /rbac/groups", auth.RequireAuth(tokenIssuer, handler.CreateGroup)) + mux.HandleFunc("POST /rbac/groups/role", auth.RequireAuth(tokenIssuer, handler.SetGroupRole)) + mux.HandleFunc("POST /rbac/groups/members", auth.RequireAuth(tokenIssuer, handler.AddGroupMember)) + mux.HandleFunc("POST /rbac/groups/members/remove", auth.RequireAuth(tokenIssuer, handler.RemoveGroupMember)) + + log.Printf("rbac-admin-api: listening on %s", addr) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Fatalf("http server: %v", err) + } +} diff --git a/deploy/systemd/nexarch-rbac-admin-api.service.tmpl b/deploy/systemd/nexarch-rbac-admin-api.service.tmpl new file mode 100644 index 0000000..0912c31 --- /dev/null +++ b/deploy/systemd/nexarch-rbac-admin-api.service.tmpl @@ -0,0 +1,14 @@ +[Unit] +Description=NEXARCH Core - Rechte-Administrations-Dienst (RBAC-05/RBAC-07) +After=network.target postgresql.service + +[Service] +Type=simple +User=nexarch +EnvironmentFile=/etc/nexarch/rbac-admin-api.env +ExecStart=__INSTALL_DIR__/bin/rbac-admin-api +Restart=on-failure +StandardOutput=journal + +[Install] +WantedBy=multi-user.target diff --git a/docs/RBAC-07-PRUEFPROTOKOLL.md b/docs/RBAC-07-PRUEFPROTOKOLL.md new file mode 100644 index 0000000..940f8ca --- /dev/null +++ b/docs/RBAC-07-PRUEFPROTOKOLL.md @@ -0,0 +1,75 @@ +# RBAC-07 – Prüfprotokoll: Rechte-Administrations-Dienst starten (RBAC-05 als laufender Dienst) + +Voraussetzung RBAC-05, IAM-16 – beide bereits Fertig, hier UNVERÄNDERT. + +## Reines Wiring, keine neue Logik + +`git diff --stat internal/rbac/` liefert KEINEN Diff. `cmd/rbac-admin-api/main.go` +setzt ausschließlich bestehende Konstruktoren zusammen und wrappt jeden +Endpunkt mit `auth.RequireAuth` — derselben Middleware, die IAM-16 +bereits nutzt. + +## JWT-Secret-Konsistenz (explizit geprüft, nicht nur konfiguriert) + +`NEXARCH_RBACADMIN_JWT_SECRET` MUSS identisch mit +`NEXARCH_ACCOUNT_JWT_SECRET` (IAM-16) sein, sonst verwirft dieser Dienst +gültige account-api-Sessions. Real getestet: mit account-api (Port +8099) eingeloggt, Session-Cookie unverändert gegen rbac-admin-api (Port +8100, ANDERER Prozess, ANDERE Portnummer) verwendet — Cookie wurde real +akzeptiert, kein zweiter Login nötig. Das beweist die Cross-Service- +Gültigkeit, nicht nur eine übereinstimmende Konfigurationszeile. + +## Bootstrap-Erkenntnis (dokumentiert) + +`internal/rbac.Handler.ListRoles` verlangt selbst bereits eine Rolle +mit `PermManageUsers` (`requireManageUsers`) — ein Benutzer OHNE +`role_assignments`-Zeile bekommt 403, auch für die reine Rollenliste. +Für den End-zu-Ende-Test wurde daher ein Testnutzer mit einer direkt in +der DB gesetzten `tenant_admin`-Bootstrap-Zeile angelegt (wie eine +Ersteinrichtung) — kein Umgehen der echten Prüfung, sondern deren +Voraussetzung. + +## Umsetzung + +- `cmd/rbac-admin-api/main.go` – Rollen/Gruppen-Endpunkte + (`GET /rbac/roles`, `POST /rbac/users/role`, + `GET /rbac/users/{id}/history`, `GET/POST /rbac/groups*`), alle + hinter `auth.RequireAuth`. +- `deploy/systemd/nexarch-rbac-admin-api.service.tmpl`. + +## Prüfungen + +| # | Prüfung | Ergebnis | +|---|---|---| +| 1 | Dienst startet und bleibt stabil (systemctl status aktiv) | **bestanden** – real auf 131: `nexarch-rbac-admin-api.service` aktiv | +| 2 | Realer Aufruf mit gültigem IAM-16-Session-Cookie liefert die erwartete Rollenliste, ohne/mit falschem Cookie 401/403 | **bestanden** – real per `curl`: mit dem bei `account-api` (IAM-16) ausgestellten Cookie → 200 mit der echten Rollen-/Rechte-Liste (`tenant_admin`, `user`); ohne Cookie → 401 | +| 3 | Code-Review: keine Änderung an internal/rbac/ selbst, nur main.go+systemd neu | **bestanden** – `git diff --stat internal/rbac/` liefert leeren Diff | + +## Echte Verdrahtung auf 192.168.1.131 + +- `rbac-admin-api` gebaut nach `/opt/nexarch-core/bin/`, + `/etc/nexarch/rbac-admin-api.env` (0600, `NEXARCH_RBACADMIN_JWT_SECRET` + identisch zu `NEXARCH_ACCOUNT_JWT_SECRET`), Dienst installiert/aktiviert. +- Migrationen `0002_role_assignments`, `0003_groups` real auf + `tenant_acme` angewendet (fehlten dort), reale Grant-Lücke gefunden + und behoben (`role_assignments`, `role_assignment_history`, `groups`, + `group_members`), über `information_schema.role_table_grants` + verifiziert. +- End-zu-Ende-Beweis: Testnutzer mit Bootstrap-`tenant_admin`-Rolle, + Login gegen `account-api`, dasselbe Cookie gegen `rbac-admin-api` + verwendet → echte Rollenliste. Testdaten anschließend entfernt. + +## Build/Test-Ergebnis (192.168.1.131) + +``` +go build ./... -> clean +go vet ./... -> clean +golangci-lint run ./cmd/rbac-admin-api/... -> 0 issues +``` + +## Gesamtergebnis + +**Bestanden.** RBAC-05 ist jetzt ein real laufender, über systemd +verwalteter Dienst, cross-service authentifiziert über dieselbe +IAM-16-Session — der zweite Baustein der browserfähigen Core-GUI nach +TEN-05.