package kek import ( "context" "encoding/base64" "encoding/json" "errors" "net/http" ) // CredentialAuthenticator ist die schmale Schnittstelle zu API-02s // Service-Credential-Pruefung (internal/moduleregistry.Registry.Authenticate). type CredentialAuthenticator interface { Authenticate(ctx context.Context, clientID, secret string) (moduleName string, ok bool, err error) } // ModuleActivationChecker ist die schmale Schnittstelle zu API-02s // Aktivierungspruefung (internal/moduleregistry.Registry.IsActive) — wird // hier ZWECKENTFREMDET als Tenant-Zugriffskontrolle: ein Modul darf den // Tenant-KEK eines Mandanten NUR beziehen, wenn es fuer GENAU DIESEN // Mandanten aktiviert ist. Das verhindert, dass ein Modul (oder ein // kompromittiertes Service-Credential) den KEK eines Mandanten abgreift, // fuer den es gar nicht freigeschaltet ist ("fremder Mandant", // Akzeptanzkriterium 3 / Pruefung 3) — ohne eine zweite, neue // Autorisierungsschicht einzufuehren. type ModuleActivationChecker interface { IsActive(ctx context.Context, tenantSlug, moduleName string) (bool, error) } // TenantResolver loest einen Tenant-Slug in seine interne ID auf // (internal/tenant.Registry.GetBySlug, TEN-01). type TenantResolver interface { ResolveTenantID(ctx context.Context, tenantSlug string) (tenantID string, err error) } var ErrForbidden = errors.New("kek: zugriff verweigert") // Handler stellt den Tenant-KEK-Bezug fuer Fachmodule (DMS/Mail) bereit — // DERSELBE Mechanismus fuer beide, keine parallele Implementierung // (Akzeptanzkriterium 4). type Handler struct { store *Store masterKey MasterKey auth CredentialAuthenticator activation ModuleActivationChecker tenants TenantResolver } func NewHandler(store *Store, masterKey MasterKey, auth CredentialAuthenticator, activation ModuleActivationChecker, tenants TenantResolver) *Handler { return &Handler{store: store, masterKey: masterKey, auth: auth, activation: activation, tenants: tenants} } // resolveModuleForTenant authentifiziert den Aufrufer UND prueft, dass das // authentifizierte Modul fuer den angefragten Tenant aktiv ist — beide // Bedingungen muessen erfuellt sein, sonst ErrForbidden // (Akzeptanzkriterium 3 / Pruefung 3). func (h *Handler) resolveModuleForTenant(ctx context.Context, clientID, secret, tenantSlug string) error { moduleName, ok, err := h.auth.Authenticate(ctx, clientID, secret) if err != nil { return err } if !ok { return ErrForbidden } active, err := h.activation.IsActive(ctx, tenantSlug, moduleName) if err != nil { return err } if !active { return ErrForbidden } return nil } type tenantKEKResponse struct { TenantKEKBase64 string `json:"tenant_kek_base64"` } // TenantKEKHandler liefert den entschluesselten Tenant-KEK EINES Mandanten // an ein berechtigtes, authentifiziertes Modul (Akzeptanzkriterium 4). func (h *Handler) TenantKEKHandler(w http.ResponseWriter, r *http.Request) { clientID := r.Header.Get("X-Nexarch-Client-Id") secret := r.Header.Get("X-Nexarch-Client-Secret") tenantSlug := r.URL.Query().Get("tenant") if tenantSlug == "" { http.Error(w, "tenant-parameter fehlt", http.StatusBadRequest) return } if err := h.resolveModuleForTenant(r.Context(), clientID, secret, tenantSlug); err != nil { if errors.Is(err, ErrForbidden) { http.Error(w, "zugriff auf diesen mandanten verweigert", http.StatusForbidden) return } http.Error(w, "interner fehler", http.StatusInternalServerError) return } tenantID, err := h.tenants.ResolveTenantID(r.Context(), tenantSlug) if err != nil { http.Error(w, "mandant nicht gefunden", http.StatusNotFound) return } plainKEK, err := h.store.GetDecrypted(r.Context(), tenantID, h.masterKey) if err != nil { http.Error(w, "tenant-kek konnte nicht ermittelt werden", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(tenantKEKResponse{TenantKEKBase64: base64.StdEncoding.EncodeToString(plainKEK)}) }