feat: support symmetric key algorithms for JWKs#2067
Conversation
|
""" WalkthroughThe changes introduce support for secret-based JWKS (JSON Web Key Set) authentication alongside existing HTTP-based JWKS. This involves extending configuration structures and schemas, updating token decoder logic to handle both remote and local secret keys, and adding comprehensive tests for mixed and secret-based JWKS authentication scenarios. Supporting utilities and configuration validation are also updated. Changes
Estimated code review effort3 (~45 minutes) 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Router image scan passed✅ No security vulnerabilities found in image: |
Router-nonroot image scan passed✅ No security vulnerabilities found in image: |
There was a problem hiding this comment.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
router/pkg/authentication/jwks_token_decoder.go (1)
41-49: Add documentation for the new fields and clarify mutual exclusivity.The new fields lack documentation explaining their purpose and usage. Additionally, the mutual exclusivity between URL-based and secret-based configurations should be documented at the struct level.
Add documentation to clarify the usage:
type JWKSConfig struct { + // URL specifies the HTTP endpoint to fetch JWK sets from. + // Either URL or Secret must be provided, but not both. URL string RefreshInterval time.Duration AllowedAlgorithms []string + // Secret is the symmetric key for HMAC-based JWT validation. + // When provided, Algorithm and KeyId must also be specified. Secret string + // Algorithm specifies the signing algorithm (e.g., HS256, HS384, HS512). + // Required when Secret is provided. Algorithm string + // KeyId is the key identifier for the secret-based JWK. + // Required when Secret is provided. KeyId string }
🧹 Nitpick comments (7)
router/pkg/config/config_test.go (1)
1202-1284: Well-structured test with good coverage of JWKS configuration scenarios.The test function effectively validates the new secret-based JWKS authentication feature with comprehensive subtests covering both valid and invalid configurations.
Minor suggestions for improvement:
- Line 1244: Consider using a realistic algorithm array instead of empty array:
- algorithms: [] + algorithms: ["RS256"]
- Test naming consistency: Consider using more descriptive test names that clearly indicate the expected behavior, e.g., "should_reject_mixed_url_and_secret_config".
router/pkg/config/config.schema.json (2)
1642-1653: Incorrect description forkey_id.Line 1652 still reads “The secret of the JWKs”, copy-pasted from the
secretfield. This should describe the key identifier instead, otherwise generated docs (and UX) will mislead users."key_id": { "type": "string", - "description": "The secret of the JWKs" + "description": "Key ID (kid) that will be set on the synthesized JWK" }
1685-1694: One-of guard blocks look good – just ask for self-documentation.The new mutual-exclusivity rules are well-formed; however, adding a brief comment above each branch clarifying “remote JWKS via URL” vs “local shared-secret JWKS” would aid future maintainers reading this ~3 000-line schema.
router/pkg/authentication/jwks_token_decoder.go (1)
97-97: Consider adding validation for secret strength.The code accepts any string as a secret without validating its strength. For HMAC algorithms, weak secrets can compromise security.
Consider adding a minimum length validation for secrets:
} + // Validate secret strength based on algorithm + minSecretLength := 32 // 256 bits minimum + if alg == jwkset.AlgHS384 { + minSecretLength = 48 // 384 bits + } else if alg == jwkset.AlgHS512 { + minSecretLength = 64 // 512 bits + } + if len(c.Secret) < minSecretLength { + return nil, fmt.Errorf("secret length (%d bytes) is below recommended minimum (%d bytes) for algorithm %s", + len(c.Secret), minSecretLength, c.Algorithm) + } jwk, err := jwkset.NewJWKFromKey([]byte(c.Secret), jwkOptions)router-tests/authentication_test.go (3)
757-804: LGTM! Consider adding test coverage for tokens from different sources.The test correctly validates authentication with multiple JWKS configurations. However, it only tests with a token from
authServer2.Consider extending the test to verify tokens from all three sources work correctly:
t.Run("authenticate when multiple jwks are present", func(t *testing.T) { authServer1, err := jwks.NewServer(t) t.Cleanup(authServer1.Close) require.NoError(t, err) authServer2, err := jwks.NewServer(t) t.Cleanup(authServer2.Close) require.NoError(t, err) - token, err := authServer2.Token(nil) + token1, err := authServer1.Token(nil) + require.NoError(t, err) + token2, err := authServer2.Token(nil) + require.NoError(t, err) t.Parallel() + secret := "example secret" + kid := "givenKID" authenticators := ConfigureAuthWithJwksConfig(t, []authentication.JWKSConfig{ { - Secret: "example secret", + Secret: secret, Algorithm: string(jwkset.AlgHS256), - KeyId: "givenKID", + KeyId: kid, }, { URL: authServer1.JWKSURL(), RefreshInterval: time.Second * 5, }, { URL: authServer2.JWKSURL(), RefreshInterval: time.Second * 5, }, }) + + token3 := generateToken(t, kid, secret, jwt.SigningMethodHS256) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ core.WithAccessController(core.NewAccessController(authenticators, true)), }, }, func(t *testing.T, xEnv *testenv.Environment) { - // Operations with a token should succeed - header := http.Header{ - "Authorization": []string{"Bearer " + token}, + // Test all three token sources + for _, token := range []string{token1, token2, token3} { + header := http.Header{ + "Authorization": []string{"Bearer " + token}, + } + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(employeesQuery)) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, JwksName, res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, employeesExpectedData, string(data)) } - res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(employeesQuery)) - require.NoError(t, err) - defer res.Body.Close() - require.Equal(t, http.StatusOK, res.StatusCode) - require.Equal(t, JwksName, res.Header.Get(xAuthenticatedByHeader)) - data, err := io.ReadAll(res.Body) - require.NoError(t, err) - require.Equal(t, employeesExpectedData, string(data)) }) })
2038-2044: Make the signing method parameter more flexible.The function parameter is currently typed as
*jwt.SigningMethodHMAC, but it could be more generic to support all signing methods.-func generateToken(t *testing.T, kid string, secret string, signingMethod *jwt.SigningMethodHMAC) string { +func generateToken(t *testing.T, kid string, secret string, signingMethod jwt.SigningMethod) string { token := jwt.New(signingMethod) token.Header[jwkset.HeaderKID] = kid jwtValue, err := token.SignedString([]byte(secret)) require.NoError(t, err) return jwtValue }
808-924: Consider adding tests for other HMAC algorithms and edge cases.The current tests focus on HS256. Consider adding test coverage for:
- HS384 and HS512 algorithms
- Invalid/unsupported algorithms
- Empty or weak secrets
- Missing required fields (Algorithm or KeyId when Secret is provided)
Would you like me to generate additional test cases for these scenarios?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
router-tests/authentication_test.go(3 hunks)router-tests/utils.go(1 hunks)router/core/supervisor_instance.go(1 hunks)router/pkg/authentication/jwks_token_decoder.go(1 hunks)router/pkg/config/config.go(1 hunks)router/pkg/config/config.schema.json(2 hunks)router/pkg/config/config_test.go(1 hunks)router/pkg/config/testdata/config_full.json(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
router/pkg/config/config.schema.json (2)
Learnt from: SkArchon
PR: #1929
File: router/internal/circuit/manager.go:16-25
Timestamp: 2025-06-30T20:39:02.387Z
Learning: In the Cosmo router project, parameter validation for circuit breaker configuration is handled at the JSON schema level rather than through runtime validation methods on structs. The config.schema.json file contains comprehensive validation constraints for circuit breaker parameters.
Learnt from: endigma
PR: #2009
File: router/pkg/config/config.go:0-0
Timestamp: 2025-07-03T10:33:25.778Z
Learning: The CardinalityLimit field in the Metrics struct (router/pkg/config/config.go) is validated at the JSON schema level in config.schema.json with a minimum value constraint of 1, preventing zero or negative values without requiring runtime validation.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: build-router
- GitHub Check: image_scan (nonroot)
- GitHub Check: build_push_image
- GitHub Check: build_push_image (nonroot)
- GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
- GitHub Check: integration_test (./telemetry)
- GitHub Check: image_scan
- GitHub Check: build_test
- GitHub Check: integration_test (./events)
- GitHub Check: build_test
- GitHub Check: Analyze (go)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
router/core/supervisor_instance.go (1)
258-262: Mutual Exclusivity Already Enforced in JSON SchemaI’ve verified that in router/pkg/config/config.schema.json the JWKS config uses a
oneOfwith a complementarynot+anyOfrule to forbidsecret,algorithmorkey_idwhenurlis present:"oneOf": [ { "required": ["url"], "not": { "anyOf": [ { "required": ["secret"] }, { "required": ["algorithm"] }, { "required": ["key_id"] } ] } }, { "required": ["secret","algorithm","key_id"], "additionalProperties": false, "properties": { … } } ]This ensures URL-based and secret-based JWKS configurations remain mutually exclusive. No further changes are required.
router/pkg/config/config.go (1)
458-464: LGTM! Clean struct extension with clear purpose.The addition of the three new fields to support secret-based JWKS configuration is well-implemented with appropriate types, YAML tags, and helpful comments explaining their purpose.
router/pkg/config/testdata/config_full.json (1)
469-473: LGTM! Test data properly updated for new fields.The addition of the three new fields (
Secret,Algorithm,KeyId) to all JWKS entries with empty string values correctly demonstrates the extended configuration structure while maintaining valid JSON syntax.Also applies to: 480-484, 488-492
router-tests/utils.go (1)
45-70: LGTM! Good refactoring to support flexible JWKS testing.The refactoring separates concerns well by extracting
ConfigureAuthWithJwksConfigto handle arbitrary JWKS configurations while preserving the originalConfigureAuthbehavior. This enables testing of both URL-based and secret-based JWKS scenarios effectively.The changes maintain:
- Original function signatures and behavior
- Proper error handling
- Test cleanup through the context
router-tests/authentication_test.go (1)
808-924: Excellent test coverage for secret-based JWKS authentication!The test comprehensively covers:
- Basic HS256 token authentication
- Mixed HTTP and secret-based JWKS configurations
- Key ID mismatch scenarios
|
@SkArchon please update the title. This would not describe the feature accurately. |
c5c761d to
842f9eb
Compare
The review comment of unclear test case name has been done and verified by ludwig (there was a similar comment in ludwig's review)
Summary by CodeRabbit
This PR adds the ability to use secrets for authorization.
Note: I used the same configuration section where "url", "algorithms", etc is present, instead of moving them into their own section (as this would be breaking backwards compatibility, and I noticed we have already done this in the recent past). However, the schema json validates that "url" and "secret" cannot be specified together.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Checklist