RET-08: ret-06-api auf rbac-06 migrieren
- internal/rbacclient: HTTP-Client fuer Core RBAC-06 (POST /authorize) - retentionapi.RequireRole (Header-Provisorium) ersetzt durch RequireRBAC, echter Aufruf gegen RBAC-06, fail-closed bei Fehlern - Mount nimmt jetzt rbacclient.Client entgegen - alle bestehenden RET-06-API-Tests weiterhin gruen - neue Tests: verweigerte Rolle (403 gegen echte RBAC-06-Antwort), erlaubte Rolle (200), RBAC-06 nicht erreichbar -> fail-closed (403) - real deployed auf 131, end-zu-ende per curl nachgewiesen (403/403/200/403) Pruefungen siehe archive/docs/RET-08-PRUEFPROTOKOLL.md
This commit is contained in:
@@ -1,22 +1,57 @@
|
||||
package retentionapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
)
|
||||
|
||||
// TestRequireRole_MissingRoleReturns403 ist die vom Nutzer geforderte
|
||||
// Negativpruefung fuer den provisorischen Rollen-Check: keine/falsche
|
||||
// Rolle => 403, sonst waere der Check nicht pruefbar.
|
||||
func TestRequireRole_MissingRoleReturns403(t *testing.T) {
|
||||
handler := RequireRole(adminRole, func(w http.ResponseWriter, r *http.Request) {
|
||||
const testServiceToken = "test-service-token"
|
||||
|
||||
// fakeRBACServer simuliert Core RBAC-06 (POST /authorize): erlaubt nur
|
||||
// die genau angegebene role/permission-Kombination, verlangt den
|
||||
// Service-Token. Damit werden RequireRBAC-Aufrufe real über HTTP
|
||||
// getestet, ohne echtes Core-Modul.
|
||||
func fakeRBACServer(t *testing.T, allowRole, allowPermission string) *rbacclient.Client {
|
||||
t.Helper()
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /authorize", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("X-Service-Token") != testServiceToken {
|
||||
http.Error(w, "kein service-token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Role string `json:"role"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
allowed := req.Role == allowRole && req.Permission == allowPermission
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]bool{"allowed": allowed})
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
t.Cleanup(server.Close)
|
||||
return rbacclient.New(server.URL, testServiceToken)
|
||||
}
|
||||
|
||||
// TestRequireRBAC_DeniedRoleReturns403 ist die vom Nutzer geforderte
|
||||
// Negativpruefung, jetzt gegen echte Policy-Rules ueber RBAC-06 (nicht
|
||||
// mehr nur ein Header-Wert wie im frueheren Provisorium).
|
||||
func TestRequireRBAC_DeniedRoleReturns403(t *testing.T) {
|
||||
client := fakeRBACServer(t, "archive_admin", "retention.configure")
|
||||
handler := RequireRBAC(client, "retention.configure", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
// Kein Header gesetzt.
|
||||
// Kein Rollen-Header.
|
||||
resp, err := http.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
@@ -26,9 +61,9 @@ func TestRequireRole_MissingRoleReturns403(t *testing.T) {
|
||||
t.Fatalf("ohne rollen-header: status = %d, want 403", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Falsche Rolle gesetzt.
|
||||
// Falsche Rolle - RBAC-06 lehnt real ab.
|
||||
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
||||
req.Header.Set("X-Admin-Roles", "irgendwas_anderes")
|
||||
req.Header.Set(requestedRoleHeader, "irgendwas_anderes")
|
||||
resp2, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
@@ -39,16 +74,18 @@ func TestRequireRole_MissingRoleReturns403(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequireRole_CorrectRoleAllowsAccess ist der positive Gegentest.
|
||||
func TestRequireRole_CorrectRoleAllowsAccess(t *testing.T) {
|
||||
handler := RequireRole(adminRole, func(w http.ResponseWriter, r *http.Request) {
|
||||
// TestRequireRBAC_AllowedRoleGrantsAccess ist der positive Gegentest,
|
||||
// real gegen die fake-RBAC-06-Antwort geprueft.
|
||||
func TestRequireRBAC_AllowedRoleGrantsAccess(t *testing.T) {
|
||||
client := fakeRBACServer(t, "archive_admin", "retention.configure")
|
||||
handler := RequireRBAC(client, "retention.configure", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
||||
req.Header.Set("X-Admin-Roles", "irgendwas_anderes, archive_admin")
|
||||
req.Header.Set(requestedRoleHeader, "archive_admin")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
@@ -58,3 +95,31 @@ func TestRequireRole_CorrectRoleAllowsAccess(t *testing.T) {
|
||||
t.Fatalf("mit korrekter rolle: status = %d, want 200", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequireRBAC_UnreachableRBACFailsClosed ist die geforderte
|
||||
// Pflichtpruefung: Ausfall von RBAC-06 fuehrt zur Ablehnung, kein
|
||||
// Fallback auf offenen Zugriff.
|
||||
func TestRequireRBAC_UnreachableRBACFailsClosed(t *testing.T) {
|
||||
unreachable := rbacclient.New("http://127.0.0.1:1", testServiceToken)
|
||||
called := false
|
||||
handler := RequireRBAC(unreachable, "retention.configure", func(w http.ResponseWriter, r *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
||||
req.Header.Set(requestedRoleHeader, "archive_admin")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Fatalf("bei nicht erreichbarem rbac-06: status = %d, want 403 (fail-closed)", resp.StatusCode)
|
||||
}
|
||||
if called {
|
||||
t.Fatal("handler haette bei nicht erreichbarem rbac-06 nie erreicht werden duerfen")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user