diff --git a/.gitea/workflows/ai-pflichttest-gate.yml b/.gitea/workflows/ai-pflichttest-gate.yml index e4c19b36..5383e51c 100644 --- a/.gitea/workflows/ai-pflichttest-gate.yml +++ b/.gitea/workflows/ai-pflichttest-gate.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - "ai/**" + - "common/**" jobs: pflichttest-gate: diff --git a/.gitea/workflows/workflow-pflichttest-gate.yml b/.gitea/workflows/workflow-pflichttest-gate.yml index d99c4de0..809692d0 100644 --- a/.gitea/workflows/workflow-pflichttest-gate.yml +++ b/.gitea/workflows/workflow-pflichttest-gate.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - "workflow/**" + - "common/**" jobs: pflichttest-gate: diff --git a/ai/cmd/ai-app/main.go b/ai/cmd/ai-app/main.go index 64f5321f..56b97d4f 100644 --- a/ai/cmd/ai-app/main.go +++ b/ai/cmd/ai-app/main.go @@ -41,9 +41,9 @@ import ( "gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearchapi" "gitea.perlbach24.de/scripte/nexarch/ai/internal/summarize" "gitea.perlbach24.de/scripte/nexarch/ai/internal/summaryapi" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/tenantregistryclient" "gitea.perlbach24.de/scripte/nexarch/ai/internal/threadsummarize" "gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex" + "gitea.perlbach24.de/scripte/nexarch/common/tenantregistryclient" ) const workerPollInterval = 2 * time.Second diff --git a/ai/go.mod b/ai/go.mod index 64a93ad3..416bab59 100644 --- a/ai/go.mod +++ b/ai/go.mod @@ -13,3 +13,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/text v0.29.0 // indirect ) + +require gitea.perlbach24.de/scripte/nexarch/common v0.0.0 + +replace gitea.perlbach24.de/scripte/nexarch/common => ../common diff --git a/ai/internal/auditclient/auditclient.go b/ai/internal/auditclient/auditclient.go deleted file mode 100644 index 74f306b3..00000000 --- a/ai/internal/auditclient/auditclient.go +++ /dev/null @@ -1,72 +0,0 @@ -// Package auditclient ist ein schlanker HTTP-Client fuer Core AUD-08 -// (internal/audit, POST /audit/ingest). AI ist ein physisch getrenntes -// Go-Modul und kann Cores internal/audit daher nicht direkt importieren - -// nur ueber dessen HTTP-Schnittstelle. Gleiches Muster wie -// connect/internal/auditclient (ENG-06) und archive/internal/auditclient. -package auditclient - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - - "gitea.perlbach24.de/scripte/nexarch/ai/internal/httpclient" -) - -type Client struct { - BaseURL string - ServiceToken string - HTTPClient *http.Client -} - -func New(baseURL, serviceToken string) *Client { - return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: httpclient.Default()} -} - -type ingestRequest struct { - TenantSlug string `json:"tenant_slug"` - Actor string `json:"actor"` - Action string `json:"action"` - Target string `json:"target"` - Metadata map[string]any `json:"metadata"` -} - -// Ingest meldet EIN Audit-Ereignis an Core. Absichtlich keine -// Update-/Delete-Methode: der Ingest-Endpunkt (AUD-08) ruft denselben -// alleinigen, anfuegenden Schreibpfad wie core-interne Aufrufer auf -// (audit.Log.Record), gegen den Core selbst per Datenbank-Trigger jede -// nachtraegliche Aenderung/Loeschung verweigert (AUD-02). -func (c *Client) Ingest(ctx context.Context, tenantSlug, actor, action, target string, metadata map[string]any) error { - body, err := json.Marshal(ingestRequest{ - TenantSlug: tenantSlug, Actor: actor, Action: action, Target: target, Metadata: metadata, - }) - if err != nil { - return fmt.Errorf("auditclient: request kodieren: %w", err) - } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/audit/ingest", bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("auditclient: 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 fmt.Errorf("auditclient: aufruf fehlgeschlagen: %w", err) - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusAccepted { - return fmt.Errorf("auditclient: unerwarteter status %d", resp.StatusCode) - } - return nil -} - -func (c *Client) httpClient() *http.Client { - if c.HTTPClient != nil { - return c.HTTPClient - } - return httpclient.Default() -} diff --git a/ai/internal/auditclient/auditclient_test.go b/ai/internal/auditclient/auditclient_test.go deleted file mode 100644 index bd256b57..00000000 --- a/ai/internal/auditclient/auditclient_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package auditclient - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" -) - -func TestIngest_SendsExpectedRequest(t *testing.T) { - var gotToken string - var gotBody ingestRequest - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gotToken = r.Header.Get("X-Service-Token") - _ = json.NewDecoder(r.Body).Decode(&gotBody) - w.WriteHeader(http.StatusAccepted) - })) - defer srv.Close() - - c := New(srv.URL, "test-token") - err := c.Ingest(context.Background(), "tenant_a", "svc:ai", "ai.decision.classify", "doc:1", - map[string]any{"model": "llama3", "prompt_version": 2}) - if err != nil { - t.Fatalf("Ingest: %v", err) - } - if gotToken != "test-token" { - t.Errorf("token = %q, want test-token", gotToken) - } - if gotBody.Action != "ai.decision.classify" || gotBody.Target != "doc:1" { - t.Errorf("unerwarteter request: %+v", gotBody) - } - if gotBody.Metadata["model"] != "llama3" { - t.Errorf("metadata.model = %v, want llama3", gotBody.Metadata["model"]) - } -} - -func TestIngest_UnexpectedStatusIsError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "zugriff verweigert", http.StatusForbidden) - })) - defer srv.Close() - - c := New(srv.URL, "test-token") - if err := c.Ingest(context.Background(), "tenant_a", "svc:ai", "ai.decision.classify", "doc:1", nil); err == nil { - t.Fatal("erwartet fehler bei status 403, habe nil") - } -} diff --git a/ai/internal/classify/classify_test.go b/ai/internal/classify/classify_test.go index ae3d0e4d..a812b75c 100644 --- a/ai/internal/classify/classify_test.go +++ b/ai/internal/classify/classify_test.go @@ -9,11 +9,11 @@ import ( "strings" "testing" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient" "gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit" "gitea.perlbach24.de/scripte/nexarch/ai/internal/provider" "gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock" "gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) var testSchema = Schema{Categories: []string{"rechnung", "vertrag", "kuendigung"}} diff --git a/ai/internal/decisionaudit/decisionaudit.go b/ai/internal/decisionaudit/decisionaudit.go index b7aeb525..3379a217 100644 --- a/ai/internal/decisionaudit/decisionaudit.go +++ b/ai/internal/decisionaudit/decisionaudit.go @@ -8,7 +8,7 @@ import ( "context" "fmt" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) // Decision ist eine einzelne, abgeschlossene KI-gestuetzte Entscheidung diff --git a/ai/internal/decisionaudit/decisionaudit_test.go b/ai/internal/decisionaudit/decisionaudit_test.go index 4c2199f9..ab713fc1 100644 --- a/ai/internal/decisionaudit/decisionaudit_test.go +++ b/ai/internal/decisionaudit/decisionaudit_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) type recordedRequest struct { diff --git a/ai/internal/docchat/docchat_qa08_loadtest_test.go b/ai/internal/docchat/docchat_qa08_loadtest_test.go index 7e40f221..5ca5124a 100644 --- a/ai/internal/docchat/docchat_qa08_loadtest_test.go +++ b/ai/internal/docchat/docchat_qa08_loadtest_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/loadtest" "gitea.perlbach24.de/scripte/nexarch/ai/internal/provider" "gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama" "gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch" + "gitea.perlbach24.de/scripte/nexarch/common/loadtest" ) // QA-08 Pruefung 1 (AK1): echte parallele Chat-Anfragen gegen den realen, diff --git a/ai/internal/docchatapi/docchatapi.go b/ai/internal/docchatapi/docchatapi.go index fd7fda38..2b5d48e5 100644 --- a/ai/internal/docchatapi/docchatapi.go +++ b/ai/internal/docchatapi/docchatapi.go @@ -12,8 +12,8 @@ import ( "gitea.perlbach24.de/scripte/nexarch/ai/internal/docchat" "gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" "gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/extractapi/extractapi.go b/ai/internal/extractapi/extractapi.go index 97fe4a39..340065ee 100644 --- a/ai/internal/extractapi/extractapi.go +++ b/ai/internal/extractapi/extractapi.go @@ -15,7 +15,7 @@ import ( "gitea.perlbach24.de/scripte/nexarch/ai/internal/extractvalidate" "gitea.perlbach24.de/scripte/nexarch/ai/internal/invoiceextract" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/extractconfirmapi/extractconfirmapi.go b/ai/internal/extractconfirmapi/extractconfirmapi.go index cecf67bb..55937bb7 100644 --- a/ai/internal/extractconfirmapi/extractconfirmapi.go +++ b/ai/internal/extractconfirmapi/extractconfirmapi.go @@ -15,7 +15,7 @@ import ( "gitea.perlbach24.de/scripte/nexarch/ai/internal/extractmapping" "gitea.perlbach24.de/scripte/nexarch/ai/internal/metadatapushclient" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/httpclient/httpclient.go b/ai/internal/httpclient/httpclient.go deleted file mode 100644 index cf7aaaf8..00000000 --- a/ai/internal/httpclient/httpclient.go +++ /dev/null @@ -1,25 +0,0 @@ -// Package httpclient liefert den HTTP-Client fuer ausgehende Dienstaufrufe -// (Kopie des Musters aus connect/internal/httpclient, ENG-06): Zeitlimit -// statt endlosem Warten, keine Weiterleitungen (ein Service-Token wuerde -// sonst an das Weiterleitungsziel mitgesendet). -package httpclient - -import ( - "net/http" - "time" -) - -const DefaultTimeout = 15 * time.Second - -var shared = New(DefaultTimeout) - -func Default() *http.Client { return shared } - -func New(timeout time.Duration) *http.Client { - return &http.Client{ - Timeout: timeout, - CheckRedirect: func(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse - }, - } -} diff --git a/ai/internal/httpclient/httpclient_test.go b/ai/internal/httpclient/httpclient_test.go deleted file mode 100644 index 5d8d3eeb..00000000 --- a/ai/internal/httpclient/httpclient_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package httpclient - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func TestNew_DoesNotFollowRedirects(t *testing.T) { - target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Fatal("weiterleitungsziel haette nie erreicht werden duerfen") - })) - defer target.Close() - - redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, target.URL, http.StatusFound) - })) - defer redirector.Close() - - resp, err := Default().Get(redirector.URL) - if err != nil { - t.Fatalf("Get: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusFound { - t.Fatalf("status = %d, want %d (keine weiterleitung gefolgt)", resp.StatusCode, http.StatusFound) - } -} diff --git a/ai/internal/loadtest/loadtest.go b/ai/internal/loadtest/loadtest.go deleted file mode 100644 index 51dc5237..00000000 --- a/ai/internal/loadtest/loadtest.go +++ /dev/null @@ -1,138 +0,0 @@ -// Package loadtest implementiert die fuer QA-07/QA-08 gemeinsam genutzte -// Last- und Leistungsmessung: parallele Sessions ausfuehren, Latenzen -// aufzeichnen, Durchsatz bestimmen. k6/vegeta sind auf diesem Rechner nicht -// installierbar (Projektregel: keine zusaetzlichen Toolchains/Dienste -// installieren) — echter Ersatz ohne externe Abhaengigkeit: reale -// nebenlaeufige Aufrufe ueber die Go-Standardbibliothek, gegen den echten, -// in dieser Sitzung gebauten Code. Eigenstaendige Kopie aus -// mail/internal/loadtest, da Go-Module keine internal/-Pakete teilen. -package loadtest - -import ( - "context" - "os" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" -) - -// Result fasst Latenz- und Durchsatzwerte eines Lasttest-Laufs zusammen. -type Result struct { - Protocol string - Concurrency int - Sessions int - Errors int - Duration time.Duration - Latencies []time.Duration // aufsteigend sortiert -} - -// Throughput liefert Sessions pro Sekunde über die Gesamtlaufzeit. -func (r Result) Throughput() float64 { - if r.Duration <= 0 { - return 0 - } - return float64(r.Sessions) / r.Duration.Seconds() -} - -// Percentile liefert die Latenz am p-ten Perzentil (0.0–1.0). -func (r Result) Percentile(p float64) time.Duration { - if len(r.Latencies) == 0 { - return 0 - } - idx := int(p * float64(len(r.Latencies)-1)) - if idx < 0 { - idx = 0 - } - if idx >= len(r.Latencies) { - idx = len(r.Latencies) - 1 - } - return r.Latencies[idx] -} - -// Max liefert die höchste gemessene Latenz. -func (r Result) Max() time.Duration { - if len(r.Latencies) == 0 { - return 0 - } - return r.Latencies[len(r.Latencies)-1] -} - -// Run führt totalSessions Sessions über maximal concurrency parallele -// Worker aus und misst je Session die Latenz von sessionFunc. -func Run(ctx context.Context, protocol string, concurrency, totalSessions int, sessionFunc func(ctx context.Context) error) Result { - var mu sync.Mutex - latencies := make([]time.Duration, 0, totalSessions) - errCount := 0 - - sem := make(chan struct{}, concurrency) - var wg sync.WaitGroup - start := time.Now() - for i := 0; i < totalSessions; i++ { - sem <- struct{}{} - wg.Add(1) - go func() { - defer wg.Done() - defer func() { <-sem }() - t0 := time.Now() - err := sessionFunc(ctx) - elapsed := time.Since(t0) - mu.Lock() - latencies = append(latencies, elapsed) - if err != nil { - errCount++ - } - mu.Unlock() - }() - } - wg.Wait() - duration := time.Since(start) - - sort.Slice(latencies, func(i, j int) bool { return latencies[i] < latencies[j] }) - return Result{ - Protocol: protocol, - Concurrency: concurrency, - Sessions: totalSessions, - Errors: errCount, - Duration: duration, - Latencies: latencies, - } -} - -// ResourceSample ist eine Momentaufnahme des Ressourcenverbrauchs DIESES -// Prozesses: Heap über die Go-Runtime, kumulierte CPU-Zeit über -// /proc/self/stat (Linux, kein externes Werkzeug nötig). -type ResourceSample struct { - HeapAllocBytes uint64 - CPUTimeSeconds float64 -} - -func SampleResources() ResourceSample { - var m runtime.MemStats - runtime.ReadMemStats(&m) - return ResourceSample{HeapAllocBytes: m.HeapAlloc, CPUTimeSeconds: readSelfCPUTimeSeconds()} -} - -// readSelfCPUTimeSeconds liefert die kumulierte User+System-CPU-Zeit dieses -// Prozesses in Sekunden. Liefert 0, wenn /proc/self/stat nicht lesbar ist -// (z. B. auf Nicht-Linux-Systemen) — Aufrufer behandeln das als "keine -// CPU-Messung verfügbar", kein harter Fehler. -func readSelfCPUTimeSeconds() float64 { - data, err := os.ReadFile("/proc/self/stat") - if err != nil { - return 0 - } - fields := strings.Fields(string(data)) - if len(fields) < 15 { - return 0 - } - utime, err1 := strconv.ParseFloat(fields[13], 64) - stime, err2 := strconv.ParseFloat(fields[14], 64) - if err1 != nil || err2 != nil { - return 0 - } - const clockTicksPerSecond = 100 // Linux-Standard-USER_HZ - return (utime + stime) / clockTicksPerSecond -} diff --git a/ai/internal/metadatapushclient/metadatapushclient.go b/ai/internal/metadatapushclient/metadatapushclient.go index ebb59afd..961e72fc 100644 --- a/ai/internal/metadatapushclient/metadatapushclient.go +++ b/ai/internal/metadatapushclient/metadatapushclient.go @@ -15,7 +15,7 @@ import ( "net/url" "sort" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) type Client struct { diff --git a/ai/internal/moduletypeapi/moduletypeapi.go b/ai/internal/moduletypeapi/moduletypeapi.go index 687254e5..e21d7d76 100644 --- a/ai/internal/moduletypeapi/moduletypeapi.go +++ b/ai/internal/moduletypeapi/moduletypeapi.go @@ -10,7 +10,7 @@ import ( "net/http" "gitea.perlbach24.de/scripte/nexarch/ai/internal/moduletype" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/pflichttestgate/gate.go b/ai/internal/pflichttestgate/gate.go index abf2265a..1796a22d 100644 --- a/ai/internal/pflichttestgate/gate.go +++ b/ai/internal/pflichttestgate/gate.go @@ -18,6 +18,7 @@ import ( // TESTSTRATEGIE-AI.md Abschnitt 3. var sensitivePathPatterns = []*regexp.Regexp{ regexp.MustCompile(`(^|/)ai/internal/svcauth/`), + regexp.MustCompile(`(^|/)common/svcauth/`), regexp.MustCompile(`(^|/)ai/internal/tenantfeature/`), regexp.MustCompile(`(^|/)ai/internal/semanticsearch/`), regexp.MustCompile(`(^|/)ai/internal/semanticsearchapi/`), diff --git a/ai/internal/qa05gate/qa05gate_test.go b/ai/internal/qa05gate/qa05gate_test.go index 586cb174..a1334331 100644 --- a/ai/internal/qa05gate/qa05gate_test.go +++ b/ai/internal/qa05gate/qa05gate_test.go @@ -16,7 +16,6 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient" "gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit" "gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate" "gitea.perlbach24.de/scripte/nexarch/ai/internal/provider" @@ -25,6 +24,7 @@ import ( "gitea.perlbach24.de/scripte/nexarch/ai/internal/residency" "gitea.perlbach24.de/scripte/nexarch/ai/internal/tenantfeature" "gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) // realPool liefert einen frischen, migrierten Pool auf der Wegwerf-DB aus diff --git a/ai/internal/queue/queue_loadtest_test.go b/ai/internal/queue/queue_loadtest_test.go index 5f664bab..43024d88 100644 --- a/ai/internal/queue/queue_loadtest_test.go +++ b/ai/internal/queue/queue_loadtest_test.go @@ -8,8 +8,8 @@ import ( "time" "gitea.perlbach24.de/scripte/nexarch/ai/internal/limits" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/loadtest" "gitea.perlbach24.de/scripte/nexarch/ai/internal/provider" + "gitea.perlbach24.de/scripte/nexarch/common/loadtest" ) // QA-08 Pruefung 2 (AK2): realistische parallele Klassifizierungs-/ diff --git a/ai/internal/residencyapi/residencyapi.go b/ai/internal/residencyapi/residencyapi.go index fb7237f0..1faf9247 100644 --- a/ai/internal/residencyapi/residencyapi.go +++ b/ai/internal/residencyapi/residencyapi.go @@ -9,7 +9,7 @@ import ( "net/http" "gitea.perlbach24.de/scripte/nexarch/ai/internal/residency" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/routingsuggestapi/routingsuggestapi.go b/ai/internal/routingsuggestapi/routingsuggestapi.go index 715326a6..7913c15a 100644 --- a/ai/internal/routingsuggestapi/routingsuggestapi.go +++ b/ai/internal/routingsuggestapi/routingsuggestapi.go @@ -13,7 +13,7 @@ import ( "gitea.perlbach24.de/scripte/nexarch/ai/internal/historysuggest" "gitea.perlbach24.de/scripte/nexarch/ai/internal/routingfeedback" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/semanticsearchapi/semanticsearchapi.go b/ai/internal/semanticsearchapi/semanticsearchapi.go index f7109630..7d8db564 100644 --- a/ai/internal/semanticsearchapi/semanticsearchapi.go +++ b/ai/internal/semanticsearchapi/semanticsearchapi.go @@ -10,7 +10,7 @@ import ( "net/http" "gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/summaryapi/summaryapi.go b/ai/internal/summaryapi/summaryapi.go index 0928ffd4..7ed052af 100644 --- a/ai/internal/summaryapi/summaryapi.go +++ b/ai/internal/summaryapi/summaryapi.go @@ -10,8 +10,8 @@ import ( "net/http" "gitea.perlbach24.de/scripte/nexarch/ai/internal/summarize" - "gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth" "gitea.perlbach24.de/scripte/nexarch/ai/internal/threadsummarize" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) type Server struct { diff --git a/ai/internal/svcauth/svcauth.go b/ai/internal/svcauth/svcauth.go deleted file mode 100644 index 5c065de3..00000000 --- a/ai/internal/svcauth/svcauth.go +++ /dev/null @@ -1,20 +0,0 @@ -// Package svcauth vergleicht Service-Token in konstanter Zeit (eigene Kopie -// je Modul, siehe workflow/internal/svcauth — Go-Module teilen keine -// internal/-Pakete über Modulgrenzen; Lehre aus RET-14/F-08: Schutz beim -// ersten Entwurf einbauen statt nachzuruesten). -package svcauth - -import ( - "crypto/sha256" - "crypto/subtle" -) - -// Equal vergleicht die SHA-256-Werte beider Eingaben in konstanter Zeit. -// Ein leeres erwartetes Token stimmt nie. -func Equal(got, want string) bool { - if want == "" { - return false - } - g, w := sha256.Sum256([]byte(got)), sha256.Sum256([]byte(want)) - return subtle.ConstantTimeCompare(g[:], w[:]) == 1 -} diff --git a/archive/cmd/compliance-api/main.go b/archive/cmd/compliance-api/main.go index 8247114b..f3d2fec2 100644 --- a/archive/cmd/compliance-api/main.go +++ b/archive/cmd/compliance-api/main.go @@ -12,9 +12,9 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "gitea.perlbach24.de/scripte/nexarch/archive/internal/complianceapi" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient" "gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" ) func main() { diff --git a/archive/cmd/kennzahlen-api/main.go b/archive/cmd/kennzahlen-api/main.go index cdd45713..e7241156 100644 --- a/archive/cmd/kennzahlen-api/main.go +++ b/archive/cmd/kennzahlen-api/main.go @@ -10,10 +10,10 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/archive/internal/kennzahlenapi" "gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient" "gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" ) func main() { diff --git a/archive/cmd/moduleadapter-api/main.go b/archive/cmd/moduleadapter-api/main.go index 8071d76d..bf12be77 100644 --- a/archive/cmd/moduleadapter-api/main.go +++ b/archive/cmd/moduleadapter-api/main.go @@ -13,10 +13,10 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/archive/internal/legalholdapi" "gitea.perlbach24.de/scripte/nexarch/archive/internal/moduleadapter" "gitea.perlbach24.de/scripte/nexarch/archive/internal/tenantregistryclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" ) func main() { diff --git a/archive/cmd/restoretest-metrics/main.go b/archive/cmd/restoretest-metrics/main.go index 71d54249..5fdeede7 100644 --- a/archive/cmd/restoretest-metrics/main.go +++ b/archive/cmd/restoretest-metrics/main.go @@ -10,8 +10,8 @@ import ( "net/http" "os" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/archive/internal/restoretest" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" ) func main() { diff --git a/archive/cmd/retention-api/main.go b/archive/cmd/retention-api/main.go index 23824942..1f083a3a 100644 --- a/archive/cmd/retention-api/main.go +++ b/archive/cmd/retention-api/main.go @@ -13,9 +13,9 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient" "gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" ) func main() { diff --git a/archive/cmd/scrub-metrics/main.go b/archive/cmd/scrub-metrics/main.go index dae7e080..da084012 100644 --- a/archive/cmd/scrub-metrics/main.go +++ b/archive/cmd/scrub-metrics/main.go @@ -14,8 +14,8 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/archive/internal/scrub" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" ) func main() { diff --git a/archive/internal/auditclient/auditclient.go b/archive/internal/auditclient/auditclient.go deleted file mode 100644 index 16f8c300..00000000 --- a/archive/internal/auditclient/auditclient.go +++ /dev/null @@ -1,70 +0,0 @@ -// Package auditclient ist ein schlanker HTTP-Client für Core AUD-08 -// (internal/audit, POST /audit/ingest). Archive ist ein physisch -// getrenntes Go-Modul und kann Cores internal/audit daher nicht direkt -// importieren — nur über dessen HTTP-Schnittstelle. Gleiches Muster wie -// archive/internal/notifyclient (CFG-05). -package auditclient - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "gitea.perlbach24.de/scripte/nexarch/common/httpclient" - "net/http" -) - -type Client struct { - BaseURL string - ServiceToken string - HTTPClient *http.Client -} - -func New(baseURL, serviceToken string) *Client { - return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: httpclient.Default()} -} - -type ingestRequest struct { - TenantSlug string `json:"tenant_slug"` - Actor string `json:"actor"` - Action string `json:"action"` - Target string `json:"target"` - Metadata map[string]any `json:"metadata"` -} - -// Ingest meldet EIN Audit-Ereignis an Core. Jeder Fehler (Transport, -// Timeout, unerwarteter Status) wird als Fehler zurückgegeben — der -// Aufrufer entscheidet, ob/wie er das nachbehandelt (siehe -// archive/internal/audittrail für den outage-toleranten Wrapper). -func (c *Client) Ingest(ctx context.Context, tenantSlug, actor, action, target string, metadata map[string]any) error { - body, err := json.Marshal(ingestRequest{ - TenantSlug: tenantSlug, Actor: actor, Action: action, Target: target, Metadata: metadata, - }) - if err != nil { - return fmt.Errorf("auditclient: request kodieren: %w", err) - } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/audit/ingest", bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("auditclient: 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 fmt.Errorf("auditclient: aufruf fehlgeschlagen: %w", err) - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusAccepted { - return fmt.Errorf("auditclient: unerwarteter status %d", resp.StatusCode) - } - return nil -} - -func (c *Client) httpClient() *http.Client { - if c.HTTPClient != nil { - return c.HTTPClient - } - return httpclient.Default() -} diff --git a/archive/internal/audittrail/audittrail.go b/archive/internal/audittrail/audittrail.go index 61fc31e7..4a13b654 100644 --- a/archive/internal/audittrail/audittrail.go +++ b/archive/internal/audittrail/audittrail.go @@ -20,7 +20,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) type Recorder struct { diff --git a/archive/internal/audittrail/audittrail_test.go b/archive/internal/audittrail/audittrail_test.go index f51d3928..8de427ee 100644 --- a/archive/internal/audittrail/audittrail_test.go +++ b/archive/internal/audittrail/audittrail_test.go @@ -11,7 +11,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) func setupOutboxTest(t *testing.T) *pgxpool.Pool { diff --git a/archive/internal/ediscovery/ediscovery_test.go b/archive/internal/ediscovery/ediscovery_test.go index 4a18b467..2975eb96 100644 --- a/archive/internal/ediscovery/ediscovery_test.go +++ b/archive/internal/ediscovery/ediscovery_test.go @@ -13,9 +13,9 @@ import ( "strings" "testing" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/auditclient" "gitea.perlbach24.de/scripte/nexarch/archive/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/archive/internal/referenceset" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" "github.com/jackc/pgx/v5/pgxpool" ) diff --git a/archive/internal/httpharden/harden.go b/archive/internal/httpharden/harden.go deleted file mode 100644 index c8b5f9b3..00000000 --- a/archive/internal/httpharden/harden.go +++ /dev/null @@ -1,116 +0,0 @@ -// Package httpharden liefert gehaertete http.Server-Grundeinstellungen -// (Timeouts, Header-Limit) und ein Body-Limit fuer alle Dienste (QA-15 F-05). -// Dieselbe Datei existiert in archive/internal/httpharden und -// dms/internal/httpharden, weil die Module getrennte go.mod-Dateien haben. -package httpharden - -import ( - "log" - "net/http" - "os" - "strconv" - "time" - - "gitea.perlbach24.de/scripte/nexarch/common/apierror" -) - -const ( - DefaultReadHeaderTimeout = 10 * time.Second - DefaultReadTimeout = 30 * time.Second - DefaultWriteTimeout = 60 * time.Second - DefaultIdleTimeout = 120 * time.Second - DefaultMaxHeaderBytes = 1 << 20 - DefaultMaxBodyBytes = 1 << 20 - - EnvReadHeaderTimeout = "NEXARCH_HTTP_READ_HEADER_TIMEOUT_SECONDS" - EnvReadTimeout = "NEXARCH_HTTP_READ_TIMEOUT_SECONDS" - EnvWriteTimeout = "NEXARCH_HTTP_WRITE_TIMEOUT_SECONDS" - EnvIdleTimeout = "NEXARCH_HTTP_IDLE_TIMEOUT_SECONDS" - EnvMaxBodyBytes = "NEXARCH_HTTP_MAX_BODY_BYTES" -) - -// Options weicht vom Standard ab. Nullwerte bedeuten Standard bzw. Env-Wert; -// StreamingBodies/StreamingResponses setzen ReadTimeout/WriteTimeout auf 0 -// (unbegrenzt), damit lange Uploads und Downloads nicht abbrechen. Schutz -// gegen langsame Header bleibt ueber ReadHeaderTimeout und IdleTimeout. -type Options struct { - StreamingBodies bool - StreamingResponses bool -} - -func envSeconds(name string, def time.Duration) time.Duration { - v := os.Getenv(name) - if v == "" { - return def - } - n, err := strconv.Atoi(v) - if err != nil || n < 1 || n > 3600 { - log.Fatalf("%s muss eine ganze Zahl zwischen 1 und 3600 sein, war %q", name, v) - } - return time.Duration(n) * time.Second -} - -// MaxBodyBytes liest die Body-Obergrenze (Standard 1 MiB, Env -// NEXARCH_HTTP_MAX_BODY_BYTES, 1 KiB bis 1 GiB). -func MaxBodyBytes() int64 { - v := os.Getenv(EnvMaxBodyBytes) - if v == "" { - return DefaultMaxBodyBytes - } - n, err := strconv.ParseInt(v, 10, 64) - if err != nil || n < 1<<10 || n > 1<<30 { - log.Fatalf("%s muss zwischen 1024 und 1073741824 liegen, war %q", EnvMaxBodyBytes, v) - } - return n -} - -// NewServer baut einen http.Server mit gehaerteten Timeouts. -func NewServer(addr string, h http.Handler, opt Options) *http.Server { - s := &http.Server{ - Addr: addr, - Handler: h, - ReadHeaderTimeout: envSeconds(EnvReadHeaderTimeout, DefaultReadHeaderTimeout), - ReadTimeout: envSeconds(EnvReadTimeout, DefaultReadTimeout), - WriteTimeout: envSeconds(EnvWriteTimeout, DefaultWriteTimeout), - IdleTimeout: envSeconds(EnvIdleTimeout, DefaultIdleTimeout), - MaxHeaderBytes: DefaultMaxHeaderBytes, - } - if opt.StreamingBodies { - s.ReadTimeout = 0 - } - if opt.StreamingResponses { - s.WriteTimeout = 0 - } - return s -} - -// ListenAndServe ersetzt http.ListenAndServe: JSON-API mit Body-Limit und -// (QA-15 F-14) apierror.Normalize aussen um jeden Handler — 5xx-Antworten -// verlieren dadurch automatisch jeden Interna-Text (Datenbank-/Pfadfehler), -// unabhaengig davon, was der einzelne Handler geschrieben hat. -func ListenAndServe(addr string, h http.Handler) error { - return NewServer(addr, apierror.Normalize(LimitBody(MaxBodyBytes(), h)), Options{}).ListenAndServe() -} - -// ListenAndServeStreaming fuer Dienste mit grossen Uploads/Downloads: keine -// globale Body-Grenze und keine Lese-/Schreib-Gesamtzeit, nur Header-Timeout. -func ListenAndServeStreaming(addr string, h http.Handler) error { - return NewServer(addr, apierror.Normalize(h), Options{StreamingBodies: true, StreamingResponses: true}).ListenAndServe() -} - -// LimitBody begrenzt Request-Bodies. Ein Content-Length ueber dem Limit wird -// vor dem Handler mit 413 abgewiesen, chunked Bodies ueber MaxBytesReader. -func LimitBody(max int64, next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.ContentLength > max { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusRequestEntityTooLarge) - _, _ = w.Write([]byte(`{"error":{"code":"payload_too_large","message":"Anfrage zu gross"}}`)) - return - } - if r.Body != nil && r.Body != http.NoBody { - r.Body = http.MaxBytesReader(w, r.Body, max) - } - next.ServeHTTP(w, r) - }) -} diff --git a/archive/internal/legalholdapi/legalholdapi.go b/archive/internal/legalholdapi/legalholdapi.go index 7c7b5e77..dec8d8c7 100644 --- a/archive/internal/legalholdapi/legalholdapi.go +++ b/archive/internal/legalholdapi/legalholdapi.go @@ -12,7 +12,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) const ( diff --git a/archive/internal/moduleadapter/handler.go b/archive/internal/moduleadapter/handler.go index 9fad7550..ad978797 100644 --- a/archive/internal/moduleadapter/handler.go +++ b/archive/internal/moduleadapter/handler.go @@ -7,7 +7,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/archive/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) const ( diff --git a/cmd/account-api/main.go b/cmd/account-api/main.go index a4b69b0e..c0a8501f 100644 --- a/cmd/account-api/main.go +++ b/cmd/account-api/main.go @@ -17,10 +17,10 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/audit" "gitea.perlbach24.de/scripte/nexarch/internal/auth" "gitea.perlbach24.de/scripte/nexarch/internal/authtoken" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/lockout" "gitea.perlbach24.de/scripte/nexarch/internal/moduletoken" "gitea.perlbach24.de/scripte/nexarch/internal/rbac" diff --git a/cmd/auditlog-devserver/main.go b/cmd/auditlog-devserver/main.go index 469f0eec..51faef16 100644 --- a/cmd/auditlog-devserver/main.go +++ b/cmd/auditlog-devserver/main.go @@ -18,10 +18,10 @@ import ( "net/http" "os" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/audit" "gitea.perlbach24.de/scripte/nexarch/internal/auditadmin" "gitea.perlbach24.de/scripte/nexarch/internal/db" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/svcauth" ) diff --git a/cmd/core/main.go b/cmd/core/main.go index 7281eb7b..baec667a 100644 --- a/cmd/core/main.go +++ b/cmd/core/main.go @@ -5,10 +5,10 @@ import ( "log" "net/http" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/audit" "gitea.perlbach24.de/scripte/nexarch/internal/config" "gitea.perlbach24.de/scripte/nexarch/internal/db" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/moduledirectory" "gitea.perlbach24.de/scripte/nexarch/internal/svcauth" "gitea.perlbach24.de/scripte/nexarch/internal/tenant" diff --git a/cmd/kek-api/main.go b/cmd/kek-api/main.go index bca2cde3..44d8ec4b 100644 --- a/cmd/kek-api/main.go +++ b/cmd/kek-api/main.go @@ -13,8 +13,8 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/flag" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/kek" "gitea.perlbach24.de/scripte/nexarch/internal/moduleregistry" "gitea.perlbach24.de/scripte/nexarch/internal/tenant" diff --git a/cmd/licadmin-devserver/main.go b/cmd/licadmin-devserver/main.go index c2e7ce98..8c0dd4bf 100644 --- a/cmd/licadmin-devserver/main.go +++ b/cmd/licadmin-devserver/main.go @@ -16,10 +16,10 @@ import ( "os" "time" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/adminapi" "gitea.perlbach24.de/scripte/nexarch/internal/db" "gitea.perlbach24.de/scripte/nexarch/internal/flag" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/license" "gitea.perlbach24.de/scripte/nexarch/internal/svcauth" "gitea.perlbach24.de/scripte/nexarch/internal/usage" diff --git a/cmd/metrics-devserver/main.go b/cmd/metrics-devserver/main.go index fbff7338..a5e5eed2 100644 --- a/cmd/metrics-devserver/main.go +++ b/cmd/metrics-devserver/main.go @@ -10,8 +10,8 @@ import ( "net/http" "os" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/db" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/metrics" ) diff --git a/cmd/notify-api/main.go b/cmd/notify-api/main.go index 4e948ad1..f945a1fc 100644 --- a/cmd/notify-api/main.go +++ b/cmd/notify-api/main.go @@ -12,7 +12,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/notify" "gitea.perlbach24.de/scripte/nexarch/internal/notifyapi" "gitea.perlbach24.de/scripte/nexarch/internal/notifyprefs" diff --git a/cmd/notifyprefs-api/main.go b/cmd/notifyprefs-api/main.go index 9fa1ce70..f1dab784 100644 --- a/cmd/notifyprefs-api/main.go +++ b/cmd/notifyprefs-api/main.go @@ -18,8 +18,8 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/auth" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/moduletrust" "gitea.perlbach24.de/scripte/nexarch/internal/notifyprefs" ) diff --git a/cmd/policy-api/main.go b/cmd/policy-api/main.go index 23919e0d..9b6fa2ea 100644 --- a/cmd/policy-api/main.go +++ b/cmd/policy-api/main.go @@ -15,8 +15,8 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/flag" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/policy" "gitea.perlbach24.de/scripte/nexarch/internal/policyapi" ) diff --git a/cmd/rbac-admin-api/main.go b/cmd/rbac-admin-api/main.go index f711e32f..7ede42ef 100644 --- a/cmd/rbac-admin-api/main.go +++ b/cmd/rbac-admin-api/main.go @@ -18,8 +18,8 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/auth" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/moduletrust" "gitea.perlbach24.de/scripte/nexarch/internal/rbac" ) diff --git a/cmd/resync-api/main.go b/cmd/resync-api/main.go index bd452b9e..0cf05faf 100644 --- a/cmd/resync-api/main.go +++ b/cmd/resync-api/main.go @@ -13,9 +13,9 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/audit" "gitea.perlbach24.de/scripte/nexarch/internal/flag" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/moduleregistry" "gitea.perlbach24.de/scripte/nexarch/internal/resync" "gitea.perlbach24.de/scripte/nexarch/internal/usage" diff --git a/cmd/secheaders-devserver/main.go b/cmd/secheaders-devserver/main.go index 67c45714..5d15c10d 100644 --- a/cmd/secheaders-devserver/main.go +++ b/cmd/secheaders-devserver/main.go @@ -10,7 +10,7 @@ import ( "net/http" "os" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/secheaders" ) diff --git a/cmd/statuspage-devserver/main.go b/cmd/statuspage-devserver/main.go index 4fc0ad21..20af6017 100644 --- a/cmd/statuspage-devserver/main.go +++ b/cmd/statuspage-devserver/main.go @@ -11,8 +11,8 @@ import ( "strconv" "time" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/db" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/statuspage" ) diff --git a/cmd/tenantadmin-devserver/main.go b/cmd/tenantadmin-devserver/main.go index 65394811..f579ddbc 100644 --- a/cmd/tenantadmin-devserver/main.go +++ b/cmd/tenantadmin-devserver/main.go @@ -12,9 +12,9 @@ import ( "net/http" "os" + "gitea.perlbach24.de/scripte/nexarch/common/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/audit" "gitea.perlbach24.de/scripte/nexarch/internal/db" - "gitea.perlbach24.de/scripte/nexarch/internal/httpharden" "gitea.perlbach24.de/scripte/nexarch/internal/svcauth" "gitea.perlbach24.de/scripte/nexarch/internal/tenant" "gitea.perlbach24.de/scripte/nexarch/internal/tenantadmin" diff --git a/connect/internal/auditclient/auditclient.go b/common/auditclient/auditclient.go similarity index 97% rename from connect/internal/auditclient/auditclient.go rename to common/auditclient/auditclient.go index bebdfb75..05fe2e61 100644 --- a/connect/internal/auditclient/auditclient.go +++ b/common/auditclient/auditclient.go @@ -12,7 +12,7 @@ import ( "fmt" "net/http" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) type Client struct { diff --git a/connect/internal/auditclient/auditclient_test.go b/common/auditclient/auditclient_test.go similarity index 100% rename from connect/internal/auditclient/auditclient_test.go rename to common/auditclient/auditclient_test.go diff --git a/internal/httpharden/harden.go b/common/httpharden/harden.go similarity index 100% rename from internal/httpharden/harden.go rename to common/httpharden/harden.go diff --git a/archive/internal/httpharden/harden_test.go b/common/httpharden/harden_test.go similarity index 100% rename from archive/internal/httpharden/harden_test.go rename to common/httpharden/harden_test.go diff --git a/mail/internal/loadtest/loadtest.go b/common/loadtest/loadtest.go similarity index 100% rename from mail/internal/loadtest/loadtest.go rename to common/loadtest/loadtest.go diff --git a/connect/internal/policyclient/policyclient.go b/common/policyclient/policyclient.go similarity index 100% rename from connect/internal/policyclient/policyclient.go rename to common/policyclient/policyclient.go diff --git a/connect/internal/policyclient/policyclient_test.go b/common/policyclient/policyclient_test.go similarity index 100% rename from connect/internal/policyclient/policyclient_test.go rename to common/policyclient/policyclient_test.go diff --git a/archive/internal/svcauth/svcauth.go b/common/svcauth/svcauth.go similarity index 100% rename from archive/internal/svcauth/svcauth.go rename to common/svcauth/svcauth.go diff --git a/archive/internal/svcauth/svcauth_test.go b/common/svcauth/svcauth_test.go similarity index 100% rename from archive/internal/svcauth/svcauth_test.go rename to common/svcauth/svcauth_test.go diff --git a/ai/internal/tenantregistryclient/tenantregistryclient.go b/common/tenantregistryclient/tenantregistryclient.go similarity index 100% rename from ai/internal/tenantregistryclient/tenantregistryclient.go rename to common/tenantregistryclient/tenantregistryclient.go diff --git a/ai/internal/tenantregistryclient/tenantregistryclient_test.go b/common/tenantregistryclient/tenantregistryclient_test.go similarity index 100% rename from ai/internal/tenantregistryclient/tenantregistryclient_test.go rename to common/tenantregistryclient/tenantregistryclient_test.go diff --git a/connect/go.mod b/connect/go.mod index 937bfb83..cef2a07d 100644 --- a/connect/go.mod +++ b/connect/go.mod @@ -13,3 +13,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/text v0.29.0 // indirect ) + +require gitea.perlbach24.de/scripte/nexarch/common v0.0.0 + +replace gitea.perlbach24.de/scripte/nexarch/common => ../common diff --git a/connect/internal/archiveclient/archiveclient.go b/connect/internal/archiveclient/archiveclient.go index 9027a81f..58cb2a3f 100644 --- a/connect/internal/archiveclient/archiveclient.go +++ b/connect/internal/archiveclient/archiveclient.go @@ -12,7 +12,7 @@ import ( "fmt" "net/http" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) type Client struct { diff --git a/connect/internal/audittrail/audittrail.go b/connect/internal/audittrail/audittrail.go index 428bc16a..e9f9b91f 100644 --- a/connect/internal/audittrail/audittrail.go +++ b/connect/internal/audittrail/audittrail.go @@ -19,7 +19,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) type Recorder struct { diff --git a/connect/internal/audittrail/audittrail_test.go b/connect/internal/audittrail/audittrail_test.go index c20708c0..b4a9d419 100644 --- a/connect/internal/audittrail/audittrail_test.go +++ b/connect/internal/audittrail/audittrail_test.go @@ -11,7 +11,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" ) func setupOutboxTest(t *testing.T) *pgxpool.Pool { diff --git a/connect/internal/backupconnector/backupconnector.go b/connect/internal/backupconnector/backupconnector.go index f6f50d6d..b9ea4e04 100644 --- a/connect/internal/backupconnector/backupconnector.go +++ b/connect/internal/backupconnector/backupconnector.go @@ -26,8 +26,8 @@ import ( "strings" "time" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/crypto" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/credential/credential_test.go b/connect/internal/credential/credential_test.go index 584523eb..adb3e0af 100644 --- a/connect/internal/credential/credential_test.go +++ b/connect/internal/credential/credential_test.go @@ -13,7 +13,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" connectcrypto "gitea.perlbach24.de/scripte/nexarch/connect/internal/crypto" diff --git a/connect/internal/crmconnector/crmconnector.go b/connect/internal/crmconnector/crmconnector.go index 8a7ce1bf..f80022a1 100644 --- a/connect/internal/crmconnector/crmconnector.go +++ b/connect/internal/crmconnector/crmconnector.go @@ -14,8 +14,8 @@ import ( "io" "net/http" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/crypto/kekprovider.go b/connect/internal/crypto/kekprovider.go index a4b67452..b4514816 100644 --- a/connect/internal/crypto/kekprovider.go +++ b/connect/internal/crypto/kekprovider.go @@ -13,7 +13,7 @@ import ( "net/http" "net/url" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) // tenantKEKResponse mirrors Core internal/kek.tenantKEKResponse (JSON diff --git a/connect/internal/erpconnector/erpconnector.go b/connect/internal/erpconnector/erpconnector.go index 2b421225..ad8698a3 100644 --- a/connect/internal/erpconnector/erpconnector.go +++ b/connect/internal/erpconnector/erpconnector.go @@ -14,8 +14,8 @@ import ( "io" "net/http" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/httpclient/httpclient.go b/connect/internal/httpclient/httpclient.go deleted file mode 100644 index 0fa05f4e..00000000 --- a/connect/internal/httpclient/httpclient.go +++ /dev/null @@ -1,25 +0,0 @@ -// Package httpclient liefert den HTTP-Client fuer ausgehende Dienstaufrufe -// (Muster aus archive/internal/httpclient, QA-15 F-11): Zeitlimit statt -// endlosem Warten, keine Weiterleitungen (ein Service-Token wuerde sonst an -// das Weiterleitungsziel mitgesendet). -package httpclient - -import ( - "net/http" - "time" -) - -const DefaultTimeout = 15 * time.Second - -var shared = New(DefaultTimeout) - -func Default() *http.Client { return shared } - -func New(timeout time.Duration) *http.Client { - return &http.Client{ - Timeout: timeout, - CheckRedirect: func(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse - }, - } -} diff --git a/connect/internal/httpclient/httpclient_test.go b/connect/internal/httpclient/httpclient_test.go deleted file mode 100644 index 0b620655..00000000 --- a/connect/internal/httpclient/httpclient_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package httpclient - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestDefaultHasTimeout(t *testing.T) { - if Default().Timeout != DefaultTimeout || DefaultTimeout <= 0 { - t.Fatalf("timeout = %s", Default().Timeout) - } -} - -func TestClientGivesUpOnHangingServer(t *testing.T) { - release := make(chan struct{}) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-release })) - defer srv.Close() - defer close(release) - - start := time.Now() - resp, err := New(200 * time.Millisecond).Get(srv.URL) - if err == nil { - _ = resp.Body.Close() - t.Fatal("kein fehler bei haengendem server") - } - if elapsed := time.Since(start); elapsed > 5*time.Second { - t.Fatalf("aufruf dauerte %s", elapsed) - } -} - -func TestClientDoesNotFollowRedirects(t *testing.T) { - target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Error("weiterleitungsziel wurde aufgerufen (Service-Token-Leck)") - })) - defer target.Close() - redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, target.URL, http.StatusTemporaryRedirect) - })) - defer redirector.Close() - - req, _ := http.NewRequest(http.MethodPost, redirector.URL, strings.NewReader("x")) - req.Header.Set("X-Service-Token", "geheim") - resp, err := Default().Do(req) - if err != nil { - t.Fatalf("do: %v", err) - } - defer func() { _ = resp.Body.Close() }() - if resp.StatusCode != http.StatusTemporaryRedirect { - t.Fatalf("status = %d, erwartet 307", resp.StatusCode) - } -} diff --git a/connect/internal/m365connector/m365connector.go b/connect/internal/m365connector/m365connector.go index 5755fb2a..0d75c1aa 100644 --- a/connect/internal/m365connector/m365connector.go +++ b/connect/internal/m365connector/m365connector.go @@ -18,8 +18,8 @@ import ( "strings" "time" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/registry" ) diff --git a/connect/internal/statusapi/statusapi.go b/connect/internal/statusapi/statusapi.go index 7455d293..7348f108 100644 --- a/connect/internal/statusapi/statusapi.go +++ b/connect/internal/statusapi/statusapi.go @@ -11,7 +11,7 @@ import ( "net/http" "time" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/policyclient" + "gitea.perlbach24.de/scripte/nexarch/common/policyclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/syncjob" ) diff --git a/connect/internal/statusapi/statusapi_policy_test.go b/connect/internal/statusapi/statusapi_policy_test.go index f6a65802..adad71f9 100644 --- a/connect/internal/statusapi/statusapi_policy_test.go +++ b/connect/internal/statusapi/statusapi_policy_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/policyclient" + "gitea.perlbach24.de/scripte/nexarch/common/policyclient" ) // newFakePolicyAPI steht fuer Core's echten POST /authorize-Vertrag (nimmt diff --git a/connect/internal/syncjob/qa02_fault_injection_test.go b/connect/internal/syncjob/qa02_fault_injection_test.go index ce6064b2..c7af840b 100644 --- a/connect/internal/syncjob/qa02_fault_injection_test.go +++ b/connect/internal/syncjob/qa02_fault_injection_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/connect/internal/credential" "gitea.perlbach24.de/scripte/nexarch/connect/internal/erpconnector" diff --git a/connect/internal/syncjob/syncjob_test.go b/connect/internal/syncjob/syncjob_test.go index e81712ee..78d80186 100644 --- a/connect/internal/syncjob/syncjob_test.go +++ b/connect/internal/syncjob/syncjob_test.go @@ -13,7 +13,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient" + "gitea.perlbach24.de/scripte/nexarch/common/auditclient" "gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/connect/internal/migrate" "gitea.perlbach24.de/scripte/nexarch/connect/internal/testdb" diff --git a/connect/internal/webhookclient/webhookclient.go b/connect/internal/webhookclient/webhookclient.go index 2762e056..0b1fe7b8 100644 --- a/connect/internal/webhookclient/webhookclient.go +++ b/connect/internal/webhookclient/webhookclient.go @@ -15,7 +15,7 @@ import ( "net/url" "time" - "gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient" + "gitea.perlbach24.de/scripte/nexarch/common/httpclient" ) type Client struct { diff --git a/dms/cmd/app/main.go b/dms/cmd/app/main.go index 5f2a7557..c544c12c 100644 --- a/dms/cmd/app/main.go +++ b/dms/cmd/app/main.go @@ -15,6 +15,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/tenantregistryclient" "gitea.perlbach24.de/scripte/nexarch/dms/internal/audittrail" "gitea.perlbach24.de/scripte/nexarch/dms/internal/authz" "gitea.perlbach24.de/scripte/nexarch/dms/internal/batchsplit" @@ -34,7 +35,6 @@ import ( "gitea.perlbach24.de/scripte/nexarch/dms/internal/searchwiring" "gitea.perlbach24.de/scripte/nexarch/dms/internal/shared" "gitea.perlbach24.de/scripte/nexarch/dms/internal/storage" - "gitea.perlbach24.de/scripte/nexarch/dms/internal/tenantregistryclient" "gitea.perlbach24.de/scripte/nexarch/dms/internal/watchfolder" ) diff --git a/dms/internal/tenantregistryclient/tenantregistryclient.go b/dms/internal/tenantregistryclient/tenantregistryclient.go deleted file mode 100644 index 1220e5c1..00000000 --- a/dms/internal/tenantregistryclient/tenantregistryclient.go +++ /dev/null @@ -1,46 +0,0 @@ -// Package tenantregistryclient ist ein schlanker HTTP-Client fuer Core TEN-01 -// (Tenant-Registry); DMS ist ein eigenes Go-Modul und kann Cores Pakete nicht -// importieren. Gleiches Muster wie archive/internal/tenantregistryclient. -package tenantregistryclient - -import ( - "context" - "fmt" - "net/http" - "net/url" - "time" -) - -type Client struct { - BaseURL string - ServiceToken string - HTTPClient *http.Client -} - -func New(baseURL, serviceToken string) *Client { - return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: &http.Client{Timeout: 10 * time.Second}} -} - -// VerifyTenant fragt Core, ob tenantSlug als Tenant gefuehrt wird. Transport- -// und Serverfehler kommen als Fehler zurueck, nie als "gueltig": der Aufrufer -// muss fail-closed reagieren. -func (c *Client) VerifyTenant(ctx context.Context, tenantSlug string) (bool, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL+"/tenants/"+url.PathEscape(tenantSlug), nil) - if err != nil { - return false, fmt.Errorf("tenantregistryclient: request bauen: %w", err) - } - req.Header.Set("X-Service-Token", c.ServiceToken) - resp, err := c.HTTPClient.Do(req) - if err != nil { - return false, fmt.Errorf("tenantregistryclient: aufruf fehlgeschlagen: %w", err) - } - defer func() { _ = resp.Body.Close() }() - switch resp.StatusCode { - case http.StatusOK: - return true, nil - case http.StatusNotFound: - return false, nil - default: - return false, fmt.Errorf("tenantregistryclient: unerwarteter status %d", resp.StatusCode) - } -} diff --git a/internal/httpharden/harden_test.go b/internal/httpharden/harden_test.go deleted file mode 100644 index 4edd2481..00000000 --- a/internal/httpharden/harden_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package httpharden - -import ( - "bufio" - "io" - "net" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestLimitBody_ContentLengthOverLimitIs413(t *testing.T) { - called := false - h := LimitBody(10, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true })) - req := httptest.NewRequest("POST", "/", strings.NewReader(strings.Repeat("a", 11))) - rec := httptest.NewRecorder() - h.ServeHTTP(rec, req) - if rec.Code != http.StatusRequestEntityTooLarge || called { - t.Fatalf("code=%d called=%v", rec.Code, called) - } -} - -func TestLimitBody_ChunkedOverLimitFailsRead(t *testing.T) { - var readErr error - h := LimitBody(10, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, readErr = io.ReadAll(r.Body) - })) - req := httptest.NewRequest("POST", "/", strings.NewReader(strings.Repeat("a", 50))) - req.ContentLength = -1 - h.ServeHTTP(httptest.NewRecorder(), req) - if readErr == nil { - t.Fatal("lesen ueber dem Limit muss fehlschlagen") - } -} - -func TestLimitBody_UnderLimitPasses(t *testing.T) { - var got string - h := LimitBody(10, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - b, _ := io.ReadAll(r.Body) - got = string(b) - })) - h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("POST", "/", strings.NewReader("abc"))) - if got != "abc" { - t.Fatalf("got %q", got) - } -} - -func TestNewServer_DefaultsAndStreaming(t *testing.T) { - s := NewServer(":0", nil, Options{}) - if s.ReadHeaderTimeout == 0 || s.ReadTimeout == 0 || s.WriteTimeout == 0 || s.IdleTimeout == 0 { - t.Fatalf("Timeouts muessen gesetzt sein: %+v", s) - } - st := NewServer(":0", nil, Options{StreamingBodies: true, StreamingResponses: true}) - if st.ReadTimeout != 0 || st.WriteTimeout != 0 || st.ReadHeaderTimeout == 0 || st.IdleTimeout == 0 { - t.Fatalf("Streaming: nur Lese-/Schreib-Gesamtzeit 0: %+v", st) - } -} - -func TestNewServer_EnvOverride(t *testing.T) { - t.Setenv(EnvReadHeaderTimeout, "3") - if got := NewServer(":0", nil, Options{}).ReadHeaderTimeout; got != 3*time.Second { - t.Fatalf("got %v", got) - } -} - -func TestMaxBodyBytes_EnvOverride(t *testing.T) { - if MaxBodyBytes() != DefaultMaxBodyBytes { - t.Fatal("Standard") - } - t.Setenv(EnvMaxBodyBytes, "2048") - if MaxBodyBytes() != 2048 { - t.Fatal("Env") - } -} - -// Slowloris: Verbindung sendet unvollstaendige Header, der Server trennt nach ReadHeaderTimeout. -func TestSlowHeadersAreClosed(t *testing.T) { - t.Setenv(EnvReadHeaderTimeout, "1") - srv := NewServer("127.0.0.1:0", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), Options{}) - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - go srv.Serve(ln) - defer srv.Close() - conn, err := net.Dial("tcp", ln.Addr().String()) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - if _, err := conn.Write([]byte("GET / HTTP/1.1\r\nHost: x\r\nX-Slow: a")); err != nil { - t.Fatal(err) - } - conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - start := time.Now() - _, err = bufio.NewReader(conn).ReadByte() - if err == nil { - t.Log("Server antwortete (z. B. 408)") - } - if time.Since(start) > 4*time.Second { - t.Fatalf("Verbindung wurde nicht nach dem Header-Timeout beendet (%v)", time.Since(start)) - } -} diff --git a/mail/internal/imap/loadtest_test.go b/mail/internal/imap/loadtest_test.go index 077f6882..6fefad47 100644 --- a/mail/internal/imap/loadtest_test.go +++ b/mail/internal/imap/loadtest_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "gitea.perlbach24.de/scripte/nexarch/mail/internal/loadtest" + "gitea.perlbach24.de/scripte/nexarch/common/loadtest" "gitea.perlbach24.de/scripte/nexarch/mail/internal/testdb" ) diff --git a/mail/internal/mailapi/mailapi.go b/mail/internal/mailapi/mailapi.go index 3565ec4b..034f7352 100644 --- a/mail/internal/mailapi/mailapi.go +++ b/mail/internal/mailapi/mailapi.go @@ -31,8 +31,8 @@ import ( "net/http" "strconv" + "gitea.perlbach24.de/scripte/nexarch/common/policyclient" "gitea.perlbach24.de/scripte/nexarch/mail/internal/moduleauth" - "gitea.perlbach24.de/scripte/nexarch/mail/internal/policyclient" "gitea.perlbach24.de/scripte/nexarch/mail/internal/search" "gitea.perlbach24.de/scripte/nexarch/mail/internal/storage" ) diff --git a/mail/internal/mailapi/mailapi_policy_test.go b/mail/internal/mailapi/mailapi_policy_test.go index 2a2f56aa..60672f2d 100644 --- a/mail/internal/mailapi/mailapi_policy_test.go +++ b/mail/internal/mailapi/mailapi_policy_test.go @@ -6,8 +6,8 @@ import ( "net/http/httptest" "testing" + "gitea.perlbach24.de/scripte/nexarch/common/policyclient" "gitea.perlbach24.de/scripte/nexarch/mail/internal/moduleauth" - "gitea.perlbach24.de/scripte/nexarch/mail/internal/policyclient" "gitea.perlbach24.de/scripte/nexarch/mail/internal/search" ) diff --git a/mail/internal/policyclient/policyclient.go b/mail/internal/policyclient/policyclient.go deleted file mode 100644 index 16c67f95..00000000 --- a/mail/internal/policyclient/policyclient.go +++ /dev/null @@ -1,52 +0,0 @@ -// Package policyclient fragt Core (RBAC-06, POST /authorize) nach der -// Rechteentscheidung; Fehler und Ausfall fuehren immer zur Ablehnung. -package policyclient - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - "time" -) - -type Client struct { - BaseURL string - ServiceToken string - HTTP *http.Client -} - -func New(baseURL, serviceToken string) *Client { - return &Client{BaseURL: strings.TrimRight(baseURL, "/"), ServiceToken: serviceToken, HTTP: &http.Client{Timeout: 5 * time.Second}} -} - -func (c *Client) Allowed(ctx context.Context, tenant, role, permission string) (bool, error) { - body := map[string]string{"role": role, "permission": permission} - if tenant != "" { - body["tenant"] = tenant - } - payload, _ := json.Marshal(body) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/authorize", bytes.NewReader(payload)) - if err != nil { - return false, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Service-Token", c.ServiceToken) - resp, err := c.HTTP.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("policy: status %d", resp.StatusCode) - } - var out struct { - Allowed *bool `json:"allowed"` - } - if err := json.NewDecoder(http.MaxBytesReader(nil, resp.Body, 1<<16)).Decode(&out); err != nil || out.Allowed == nil { - return false, fmt.Errorf("policy: antwort ohne entscheidung") - } - return *out.Allowed, nil -} diff --git a/mail/internal/pop3/loadtest_test.go b/mail/internal/pop3/loadtest_test.go index 4493b5f9..43de6fae 100644 --- a/mail/internal/pop3/loadtest_test.go +++ b/mail/internal/pop3/loadtest_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "gitea.perlbach24.de/scripte/nexarch/mail/internal/loadtest" + "gitea.perlbach24.de/scripte/nexarch/common/loadtest" "gitea.perlbach24.de/scripte/nexarch/mail/internal/testdb" ) diff --git a/mail/internal/smtp/loadtest_test.go b/mail/internal/smtp/loadtest_test.go index f274285a..5af13705 100644 --- a/mail/internal/smtp/loadtest_test.go +++ b/mail/internal/smtp/loadtest_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "gitea.perlbach24.de/scripte/nexarch/mail/internal/loadtest" + "gitea.perlbach24.de/scripte/nexarch/common/loadtest" "gitea.perlbach24.de/scripte/nexarch/mail/internal/testdb" ) diff --git a/workflow/cmd/workflow-app/main.go b/workflow/cmd/workflow-app/main.go index 1b637622..b5298ce3 100644 --- a/workflow/cmd/workflow-app/main.go +++ b/workflow/cmd/workflow-app/main.go @@ -16,6 +16,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" + "gitea.perlbach24.de/scripte/nexarch/common/tenantregistryclient" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/approval" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/approvalapi" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/backchannel" @@ -33,7 +34,6 @@ import ( "gitea.perlbach24.de/scripte/nexarch/workflow/internal/routingruleapi" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/routingsuggestclient" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/task" - "gitea.perlbach24.de/scripte/nexarch/workflow/internal/tenantregistryclient" ) func requireEnv(name string) string { diff --git a/workflow/go.mod b/workflow/go.mod index 60e3336a..6e965b14 100644 --- a/workflow/go.mod +++ b/workflow/go.mod @@ -11,3 +11,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/text v0.29.0 // indirect ) + +require gitea.perlbach24.de/scripte/nexarch/common v0.0.0 + +replace gitea.perlbach24.de/scripte/nexarch/common => ../common diff --git a/workflow/internal/moduleadapter/handler.go b/workflow/internal/moduleadapter/handler.go index b8e043d3..22123c87 100644 --- a/workflow/internal/moduleadapter/handler.go +++ b/workflow/internal/moduleadapter/handler.go @@ -10,7 +10,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" - "gitea.perlbach24.de/scripte/nexarch/workflow/internal/svcauth" + "gitea.perlbach24.de/scripte/nexarch/common/svcauth" ) // TenantVerifier prueft einen vom aufrufenden Modul mitgelieferten diff --git a/workflow/internal/pflichttestgate/gate.go b/workflow/internal/pflichttestgate/gate.go index f6173167..d597a3ec 100644 --- a/workflow/internal/pflichttestgate/gate.go +++ b/workflow/internal/pflichttestgate/gate.go @@ -18,8 +18,10 @@ import ( // workflow/docs/TESTSTRATEGIE.md Abschnitt 4. var sensitivePathPatterns = []*regexp.Regexp{ regexp.MustCompile(`(^|/)workflow/internal/svcauth/`), + regexp.MustCompile(`(^|/)common/svcauth/`), regexp.MustCompile(`(^|/)workflow/internal/moduleadapter/`), regexp.MustCompile(`(^|/)workflow/internal/tenantregistryclient/`), + regexp.MustCompile(`(^|/)common/tenantregistryclient/`), regexp.MustCompile(`(^|/)workflow/internal/backchannel/`), regexp.MustCompile(`(^|/)workflow/internal/backchannelapi/`), regexp.MustCompile(`(^|/)workflow/internal/dualcheck/`), diff --git a/workflow/internal/svcauth/svcauth.go b/workflow/internal/svcauth/svcauth.go deleted file mode 100644 index 7da2bc8a..00000000 --- a/workflow/internal/svcauth/svcauth.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package svcauth vergleicht Service-Token in konstanter Zeit (Muster aus -// archive/internal/svcauth, Lehre aus RET-14/F-08: ohne diesen Schutz -// beim ersten Entwurf des Adapters einbauen statt nachzuruesten). -package svcauth - -import ( - "crypto/sha256" - "crypto/subtle" -) - -// Equal vergleicht die SHA-256-Werte beider Eingaben in konstanter Zeit. -// Ein leeres erwartetes Token stimmt nie. -func Equal(got, want string) bool { - if want == "" { - return false - } - g, w := sha256.Sum256([]byte(got)), sha256.Sum256([]byte(want)) - return subtle.ConstantTimeCompare(g[:], w[:]) == 1 -} diff --git a/workflow/internal/tenantregistryclient/tenantregistryclient.go b/workflow/internal/tenantregistryclient/tenantregistryclient.go deleted file mode 100644 index d2a803ef..00000000 --- a/workflow/internal/tenantregistryclient/tenantregistryclient.go +++ /dev/null @@ -1,47 +0,0 @@ -// Package tenantregistryclient ist ein schlanker HTTP-Client fuer Core TEN-01 -// (Tenant-Registry); Workflow ist ein eigenes Go-Modul und kann Cores Pakete -// nicht importieren. Gleiches Muster wie dms/internal/tenantregistryclient -// und archive/internal/tenantregistryclient. -package tenantregistryclient - -import ( - "context" - "fmt" - "net/http" - "net/url" - "time" -) - -type Client struct { - BaseURL string - ServiceToken string - HTTPClient *http.Client -} - -func New(baseURL, serviceToken string) *Client { - return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: &http.Client{Timeout: 10 * time.Second}} -} - -// VerifyTenant fragt Core, ob tenantSlug als Tenant gefuehrt wird. Transport- -// und Serverfehler kommen als Fehler zurueck, nie als "gueltig": der Aufrufer -// muss fail-closed reagieren. -func (c *Client) VerifyTenant(ctx context.Context, tenantSlug string) (bool, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL+"/tenants/"+url.PathEscape(tenantSlug), nil) - if err != nil { - return false, fmt.Errorf("tenantregistryclient: request bauen: %w", err) - } - req.Header.Set("X-Service-Token", c.ServiceToken) - resp, err := c.HTTPClient.Do(req) - if err != nil { - return false, fmt.Errorf("tenantregistryclient: aufruf fehlgeschlagen: %w", err) - } - defer func() { _ = resp.Body.Close() }() - switch resp.StatusCode { - case http.StatusOK: - return true, nil - case http.StatusNotFound: - return false, nil - default: - return false, fmt.Errorf("tenantregistryclient: unerwarteter status %d", resp.StatusCode) - } -}