mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-21 06:37:21 +00:00
fix(auth): verify JWT signature to prevent cache-key forgery (GHSA-9gqw-h2rw-44wv)
The proxy derived per-user GraphQL cache keys from JWT claims without verifying the token signature (CWE-345). A remote attacker could forge a token that carried a victim's claim value and read the victim's cached response. This change adds opt-in signature verification behind JWT_VERIFY_SIGNATURE. The flag defaults to false, so an upgrade without a config change keeps the previous delegate-to-upstream behavior byte for byte. When verification is on, the operator must set exactly one key source: JWT_SECRET, JWT_PUBLIC_KEY, or JWT_JWKS_URL. The verifier pins the accepted algorithms to the key family through WithValidMethods and a type-switched keyfunc, and it refuses alg=none. This closes the algorithm-confusion class of attack. A present but invalid token now returns 401. A missing Authorization header stays anonymous. Startup fails loudly when verification is on and the key source is absent or unreadable, so the proxy can never boot half-configured.
This commit is contained in:
+33
-8
@@ -15,31 +15,56 @@ const defaultValue = "-"
|
||||
|
||||
var emptyMetrics = map[string]string{}
|
||||
|
||||
func extractClaimsFromJWTHeader(authorization string) (usr, role string) {
|
||||
// extractClaimsFromJWTHeader extracts the user ID and role claim from an
|
||||
// Authorization header value.
|
||||
//
|
||||
// When cfg.Client.JWTVerifier is set (JWT_VERIFY_SIGNATURE=true), the token
|
||||
// signature is checked first; a signature, expiry, or algorithm failure
|
||||
// returns a non-nil err and the caller must not trust usr/role (this is the
|
||||
// fix for GHSA-9gqw-h2rw-44wv, where a forged token with a guessed claim
|
||||
// could otherwise read another user's cached response).
|
||||
//
|
||||
// When JWTVerifier is nil (the default), this falls back to the legacy
|
||||
// path: the payload is decoded without checking its signature, exactly as
|
||||
// before this field existed. err is always nil on that path, matching
|
||||
// pre-verification behaviour byte for byte.
|
||||
func extractClaimsFromJWTHeader(authorization string) (usr, role string, err error) {
|
||||
usr, role = defaultValue, defaultValue
|
||||
|
||||
if cfg.Client.JWTVerifier != nil {
|
||||
claimMap, verr := cfg.Client.JWTVerifier.Verify(authorization)
|
||||
if verr != nil {
|
||||
handleError("JWT verification failed", map[string]any{"token": maskToken(authorization)})
|
||||
return defaultValue, defaultValue, verr
|
||||
}
|
||||
|
||||
usr = extractClaim(claimMap, cfg.Client.JWTUserClaimPath, "user id")
|
||||
role = extractClaim(claimMap, cfg.Client.JWTRoleClaimPath, "role")
|
||||
return usr, role, nil
|
||||
}
|
||||
|
||||
tokenParts := strings.SplitN(authorization, ".", 3)
|
||||
if len(tokenParts) != 3 {
|
||||
handleError("Can't split the token", map[string]any{"token": maskToken(authorization)})
|
||||
return
|
||||
return usr, role, nil
|
||||
}
|
||||
|
||||
claim, err := base64.RawURLEncoding.DecodeString(tokenParts[1])
|
||||
if err != nil {
|
||||
claim, decErr := base64.RawURLEncoding.DecodeString(tokenParts[1])
|
||||
if decErr != nil {
|
||||
handleError("Can't decode the token", map[string]any{"token": maskToken(authorization)})
|
||||
return
|
||||
return usr, role, nil
|
||||
}
|
||||
|
||||
var claimMap map[string]any
|
||||
if err = json.Unmarshal(claim, &claimMap); err != nil {
|
||||
if jsonErr := json.Unmarshal(claim, &claimMap); jsonErr != nil {
|
||||
handleError("Can't unmarshal the claim", map[string]any{"token": maskToken(authorization)})
|
||||
return
|
||||
return usr, role, nil
|
||||
}
|
||||
|
||||
usr = extractClaim(claimMap, cfg.Client.JWTUserClaimPath, "user id")
|
||||
role = extractClaim(claimMap, cfg.Client.JWTRoleClaimPath, "role")
|
||||
|
||||
return
|
||||
return usr, role, nil
|
||||
}
|
||||
|
||||
func extractClaim(claimMap map[string]any, claimPath, name string) string {
|
||||
|
||||
Reference in New Issue
Block a user