Files
nexarch/archive/internal/rbacclient/rbacclient.go
T
sysops b30e16cb4f 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
2026-08-30 08:43:00 +02:00

83 lines
2.5 KiB
Go

// Package rbacclient ist ein schlanker HTTP-Client für Core RBAC-06
// (internal/policyapi, POST /authorize). Archive ist ein physisch
// getrenntes Go-Modul und kann Core RBAC-02 (internal/policy) daher
// nicht direkt importieren — nur über die HTTP-Schnittstelle von
// RBAC-06 aufrufen.
package rbacclient
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
)
// ErrDenied wird zurückgegeben, wenn RBAC-06 die Anfrage ausdrücklich
// ablehnt (kein Transport-/Serverfehler, sondern eine bewusste
// Policy-Entscheidung).
var ErrDenied = errors.New("rbac: zugriff verweigert")
// Client ruft den RBAC-06-Endpunkt auf.
type Client struct {
BaseURL string
ServiceToken string
HTTPClient *http.Client
}
func New(baseURL, serviceToken string) *Client {
return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: http.DefaultClient}
}
type authorizeRequest struct {
Role string `json:"role"`
Permission string `json:"permission"`
}
type authorizeResponse struct {
Allowed bool `json:"allowed"`
}
// Authorize fragt RBAC-06, ob role die permission besitzt. Jeder
// Fehler (Transport, Timeout, unerwarteter Status) wird als Fehler
// zurückgegeben, NICHT stillschweigend als "erlaubt" behandelt — der
// Aufrufer muss bei einem Fehler fail-closed (verweigern) reagieren.
func (c *Client) Authorize(ctx context.Context, role, permission string) (bool, error) {
body, err := json.Marshal(authorizeRequest{Role: role, Permission: permission})
if err != nil {
return false, fmt.Errorf("rbac: request kodieren: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/authorize", bytes.NewReader(body))
if err != nil {
return false, fmt.Errorf("rbac: request bauen: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Service-Token", c.ServiceToken)
resp, err := c.httpClient().Do(req)
if err != nil {
return false, fmt.Errorf("rbac: aufruf fehlgeschlagen: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("rbac: unerwarteter status %d", resp.StatusCode)
}
var out authorizeResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return false, fmt.Errorf("rbac: antwort dekodieren: %w", err)
}
if !out.Allowed {
return false, ErrDenied
}
return true, nil
}
func (c *Client) httpClient() *http.Client {
if c.HTTPClient != nil {
return c.HTTPClient
}
return http.DefaultClient
}