- 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
126 lines
4.3 KiB
Go
126 lines
4.3 KiB
Go
package retentionapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
|
)
|
|
|
|
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 Rollen-Header.
|
|
resp, err := http.Get(server.URL)
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
if resp.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("ohne rollen-header: status = %d, want 403", resp.StatusCode)
|
|
}
|
|
|
|
// Falsche Rolle - RBAC-06 lehnt real ab.
|
|
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
|
req.Header.Set(requestedRoleHeader, "irgendwas_anderes")
|
|
resp2, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
defer func() { _ = resp2.Body.Close() }()
|
|
if resp2.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("mit falscher rolle: status = %d, want 403", resp2.StatusCode)
|
|
}
|
|
}
|
|
|
|
// 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(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.StatusOK {
|
|
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")
|
|
}
|
|
}
|