hashicorp/nomad · error

unable to get validation keys from JWKS: %v

Error message

unable to get validation keys from JWKS: %v

What it means

usingJWKS fails when jwt.NewJSONWebKeySet cannot fetch or parse the JWKS document from the configured jwks_url. Nomad fetches signing keys from the provider's JWKS endpoint at login time; any network, TLS, or format problem is reported here.

Source

Thrown at lib/auth/jwt/validator.go:107

func usingStaticKeys(keys []string) (jwt.KeySet, error) {
	var parsedKeys []crypto.PublicKey
	for _, v := range keys {
		key, err := jwt.ParsePublicKeyPEM([]byte(v))
		parsedKeys = append(parsedKeys, key)
		if err != nil {
			return nil, fmt.Errorf("unable to parse public key for JWT auth: %v", err)
		}
	}
	return jwt.NewStaticKeySet(parsedKeys)
}

func usingJWKS(ctx context.Context, jwksurl, jwkscapem string) (jwt.KeySet, error) {
	// Measure the JWKS endpoint performance.
	defer metrics.MeasureSince([]string{"nomad", "acl", "jwt", "jwks"}, time.Now())

	keySet, err := jwt.NewJSONWebKeySet(ctx, jwksurl, jwkscapem)
	if err != nil {
		return nil, fmt.Errorf("unable to get validation keys from JWKS: %v", err)
	}
	return keySet, nil
}

func usingOIDC(ctx context.Context, oidcurl string, oidccapem []string) (jwt.KeySet, error) {
	// Measure the OIDC endpoint performance.
	defer metrics.MeasureSince([]string{"nomad", "acl", "jwt", "oidc_jwt"}, time.Now())

	// TODO why do we have DiscoverCaPem as an array but JWKSCaPem as a single string?
	pem := ""
	if len(oidccapem) > 0 {
		pem = oidccapem[0]
	}

	keySet, err := jwt.NewOIDCDiscoveryKeySet(ctx, oidcurl, pem)
	if err != nil {
		return nil, fmt.Errorf("unable to get validation keys from OIDC provider: %v", err)
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. From a Nomad server node, test reachability: `curl -v <jwks_url>` and confirm valid JWKS JSON (keys array) is returned.
  2. If the endpoint uses a private CA, set JWKSCACert on the auth method to the CA's PEM.
  3. Correct the jwks_url in the auth method config (usually `<issuer>/.well-known/jwks.json` or the provider's documented JWKS path).
  4. Check proxy/firewall rules allowing egress from all Nomad servers.

Example fix

// before
cfg := &structs.ACLAuthMethodConfig{
  JwksURL: "https://keycloak.internal/realms/prod/protocol/openid-connect/certs",
}
// after: trust the internal CA
cfg := &structs.ACLAuthMethodConfig{
  JwksURL:     "https://keycloak.internal/realms/prod/protocol/openid-connect/certs",
  JWKSCACert:  "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
}
Defensive patterns

Strategy: validation

Validate before calling

resp, err := http.Get(jwksURL)
if err != nil { return fmt.Errorf("JWKS unreachable: %w", err) }
if resp.StatusCode != 200 { return fmt.Errorf("JWKS returned %d", resp.StatusCode) }
var doc struct{ Keys []map[string]any `json:"keys"` }
if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil || len(doc.Keys) == 0 {
  return fmt.Errorf("JWKS body invalid")
}

Try / catch

keySet, err := usingJWKS(ctx, jwksURL, caPEM)
if err != nil {
  return fmt.Errorf("check JwksURL reachability from Nomad servers and JWKSCACert: %w", err)
}

Prevention

When it happens

Trigger: Validate → usingJWKS when the auth method has JwksURL set and the HTTP GET of the JWKS URL fails or returns unparseable JSON, or the optional JWKSCACert does not validate the endpoint's TLS chain.

Common situations: JWKS URL unreachable from Nomad servers (firewall, DNS); private/internal IdP endpoint with a self-signed or private CA cert not supplied via JWKSCACert; provider returns non-200/HTML error page; typo'd URL.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/6e37db08e12f4bbb. Report an issue: GitHub.