fix(cache): harden cache backends, resilience, recovery, and transports

- RESP -ERR no longer closes healthy conn / aborts batch; global cap replacement-aware
- global health-check pass serialized (latent data race); MemoryMonitor via ResourceManager
- token-exchange retry, open-circuit rejections counted as failures
- TLS-posture matching transport reuse (no silent InsecureSkipVerify handoff)
This commit is contained in:
2026-08-14 16:16:51 +01:00
parent 8640451877
commit 4d23d618f5
49 changed files with 2318 additions and 181 deletions
+97 -3
View File
@@ -10,6 +10,7 @@ import (
"html"
"net/http"
"runtime"
"strconv"
"strings"
"time"
)
@@ -136,9 +137,7 @@ func (t *TraefikOidc) isAllowedDomain(email string) bool {
}
domain := strings.ToLower(parts[1])
_, domainAllowed := t.allowedUserDomains[domain]
if domainAllowed {
if t.domainAllowed(domain) {
t.logger.Debugf("Email domain %s is allowed", domain)
return true
} else {
@@ -153,6 +152,97 @@ func (t *TraefikOidc) isAllowedDomain(email string) bool {
return false
}
// domainAllowed reports whether domain is an allowed user domain,
// including subdomains thereof. An operator configuring
// "example.com" intends to cover staff on corporate subdomains, so
// "mail.example.com" (and deeper) must be accepted; exact match is
// no longer the only path. A plain suffix lookup is avoided (would
// also match "badexample.com"), so subdomains require a leading "."
// separator. (R152)
func (t *TraefikOidc) domainAllowed(domain string) bool {
if _, ok := t.allowedUserDomains[domain]; ok {
return true
}
for allowed := range t.allowedUserDomains {
if len(domain) > len(allowed) && strings.HasSuffix(domain, "."+allowed) {
return true
}
}
return false
}
// emailVerifiedTrue extracts the boolean email_verified claim value,
// claimScalarString returns the string form of a scalar claim value,
// coercing JSON numbers (float64, json.Number, int variants) to their
// decimal string representation so a numeric identifier claim (e.g. a
// numeric sub from a non-conformant IdP) is preserved rather than
// silently dropped. second is false for non-scalar values (maps,
// slices, objects) so callers can treat them as invalid.
func claimScalarString(raw interface{}) (s string, ok bool) {
switch v := raw.(type) {
case string:
return v, true
case json.Number:
return v.String(), true
case float64:
return strconv.FormatFloat(v, 'f', -1, 64), true
case float32:
return strconv.FormatFloat(float64(v), 'f', -1, 32), true
case int:
return strconv.Itoa(v), true
case int64:
return strconv.FormatInt(v, 10), true
default:
return "", false
}
}
// emailVerifiedTrue extracts the boolean email_verified claim value,
// tolerating bool and string representations. present is false when the
// claim is absent or of an uninterpretable type (the caller then decides
// how to treat absence).
func emailVerifiedTrue(v interface{}) (verified, present bool) {
switch val := v.(type) {
case bool:
return val, true
case string:
switch strings.ToLower(strings.TrimSpace(val)) {
case "true", "1":
return true, true
case "false", "0":
return false, true
}
case float64:
return val != 0, true
case json.Number:
f, err := val.Float64()
if err != nil {
return false, false
}
return f != 0, true
}
return false, false
}
// emailIdentityPermitted reports whether an email-based user identifier may be
// admitted for email/domain authorization given the ID-token claims. An email
// is admitted unless the IdP explicitly marks it unverified
// (email_verified == false): such an address must not satisfy an email
// allowlist, since the user may not control it (impersonation within an
// allowed domain). Absence is tolerated for backward compatibility (the
// middleware and its test fixtures have long run without the claim; some IdPs
// do not emit it). This matches the bearer-path stance that untrusted email
// is a spoofing vector (main.go).
func emailIdentityPermitted(userIdentifier string, claims map[string]interface{}) bool {
if !strings.Contains(userIdentifier, "@") {
return true
}
if verified, present := emailVerifiedTrue(claims["email_verified"]); present && !verified {
return false
}
return true
}
// keysFromMap extracts string keys from a map for logging purposes.
// Helper function to get keys from a map for logging.
// Parameters:
@@ -176,6 +266,10 @@ func keysFromMap(m map[string]struct{}) []string {
// - message: The error message to display.
// - code: The HTTP status code to set for the response.
func (t *TraefikOidc) sendErrorResponse(rw http.ResponseWriter, req *http.Request, message string, code int) {
// Auth-failure responses (401/403/429/503) must not be cached by
// browsers or intermediaries — a cached 401 body would persist across
// re-authentication (R101).
rw.Header().Set("Cache-Control", "no-store")
acceptHeader := req.Header.Get("Accept")
if strings.Contains(acceptHeader, "application/json") {