CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Signaturschluessel der Modul-JWTs liegen verschluesselt in der Mandanten-Datenbank (module_signing_keys), werden von mehreren Instanzen geteilt und rotieren mit Ueberlappung; nicht lesbarer Speicher ergibt 503 statt Token/JWKS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
77 lines
2.6 KiB
Go
77 lines
2.6 KiB
Go
package moduletoken
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/moduletrust"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/rbac"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/user"
|
|
)
|
|
|
|
type passthroughCipher struct{}
|
|
|
|
func (passthroughCipher) Encrypt(pt []byte) ([]byte, error) { return append([]byte(nil), pt...), nil }
|
|
func (passthroughCipher) Decrypt(ct []byte) ([]byte, error) { return append([]byte(nil), ct...), nil }
|
|
|
|
// Ist der Schluesselspeicher nicht lesbar, gibt es weder Token (503) noch JWKS (503) — nach Wiederanlauf wieder beides.
|
|
func TestPersistentKeys_StoreOutageYields503ForTokenAndJWKS(t *testing.T) {
|
|
now := time.Now()
|
|
store := moduletrust.NewMemKeyStore()
|
|
km, err := moduletrust.NewPersistentKeyManager(context.Background(), moduletrust.PersistentConfig{
|
|
Store: store, Cipher: passthroughCipher{}, TokenTTL: time.Minute,
|
|
ActivationDelay: time.Minute, RefreshInterval: time.Second,
|
|
Now: func() time.Time { return now },
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sessions := auth.NewTokenIssuer("test-secret-fuer-sitzungen")
|
|
mux := http.NewServeMux()
|
|
RegisterJWKS(mux, km)
|
|
RegisterToken(mux, NewHandler(km, &fakeUsers{u: user.User{ID: "u1", Status: user.StatusActive}}, &fakeRoles{role: rbac.RoleUser}, "acme", time.Minute),
|
|
sessions, revHolder{&fakeRevocation{}})
|
|
srv := httptest.NewServer(mux)
|
|
defer srv.Close()
|
|
session, err := sessions.Issue("u1", "acme")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
call := func(method, path string, withCookie bool) int {
|
|
req, _ := http.NewRequest(method, srv.URL+path, nil)
|
|
if withCookie {
|
|
req.AddCookie(&http.Cookie{Name: auth.CookieName, Value: session})
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp.Body.Close()
|
|
return resp.StatusCode
|
|
}
|
|
|
|
if got := call(http.MethodPost, TokenPath, true); got != http.StatusOK {
|
|
t.Fatalf("token bei laufendem speicher: %d", got)
|
|
}
|
|
now = now.Add(5 * time.Second)
|
|
store.SetFailing(true)
|
|
if got := call(http.MethodPost, TokenPath, true); got != http.StatusServiceUnavailable {
|
|
t.Fatalf("token bei speicherausfall: %d, erwartet 503", got)
|
|
}
|
|
if got := call(http.MethodGet, JWKSPath, false); got != http.StatusServiceUnavailable {
|
|
t.Fatalf("jwks bei speicherausfall: %d, erwartet 503", got)
|
|
}
|
|
store.SetFailing(false)
|
|
if got := call(http.MethodPost, TokenPath, true); got != http.StatusOK {
|
|
t.Fatalf("token nach wiederanlauf: %d", got)
|
|
}
|
|
if got := call(http.MethodGet, JWKSPath, false); got != http.StatusOK {
|
|
t.Fatalf("jwks nach wiederanlauf: %d", got)
|
|
}
|
|
}
|