Skip to content

Conversation

vertex451
Copy link
Contributor

@vertex451 vertex451 commented Oct 3, 2025

Changes

  • GatewayShouldImpersonate=false used to work with any token due to adminRT usage. I fixed it by replacing adminRT with baseRT with no admin credentials. Now it works only with valid token.
  • Fixed auth check to proceed without a token if IntrospectionAuthentication = false
  • Simplified code by removal of the Roundtripper factory.
  • In the config moved IntrospectionAuthentication into the Gateway section, since it is not related to the listener.

Summary by CodeRabbit

  • Refactor

    • Renamed/moved configuration: introspection-authentication is now gateway-introspection-authentication under the Gateway settings. Update configs accordingly.
    • Simplified internal HTTP transport setup with no expected user-visible behavior changes.
  • CLI

    • Updated flag: use --gateway-introspection-authentication; the old introspection-authentication default was removed.
  • Tests

    • Updated test suite to reflect the new gateway-introspection-authentication configuration path.

@vertex451 vertex451 requested a review from a team as a code owner October 3, 2025 14:44
Copy link

coderabbitai bot commented Oct 3, 2025

Walkthrough

Removed the external RoundTripper factory from manager and targetcluster APIs, moved introspection auth into the nested Gateway config, updated roundtripper internals to accept a base transport and clone requests, and updated related constructors and tests to match the new signatures and wiring.

Changes

Cohort / File(s) Summary
Manager init change
gateway/manager/manager.go
Stopped passing a RoundTripper factory into the cluster registry; now calls NewClusterRegistry(log, appCfg).
RoundTripper implementation
gateway/manager/roundtripper/roundtripper.go
Added baseRT field and baseRoundTripper parameter to New; clones requests before header edits; uses transport.NewBearerAuthRoundTripper(..., baseRT) for non-impersonation flows; constructor signature expanded.
RoundTripper tests
gateway/manager/roundtripper/roundtripper_test.go
Updated New(...) call sites to pass an additional base/admin RoundTripper (often same mock); tests adjusted/removed to reflect new API.
Target cluster wiring
gateway/manager/targetcluster/cluster.go
Removed external roundTripperFactory param; added internal wrapping that unwraps to a base transport (unwrapToBaseTransport) and constructs internal roundtripper via roundtripper.New; updated NewTargetCluster and connect signatures.
Cluster registry API
gateway/manager/targetcluster/registry.go
Removed exported RoundTripperFactory type/field; NewClusterRegistry signature now (log, appCfg); LoadCluster and auth gating updated to not use external factory.
Registry tests
gateway/manager/targetcluster/registry_test.go
Updated constructor calls to NewClusterRegistry(log, appCfg) (removed nil factory arg).
Config structure move
common/config/config.go
Moved IntrospectionAuthentication from top-level Config into nested Gateway struct and changed its mapstructure key to gateway-introspection-authentication.
Config tests
common/config/config_test.go
Updated tests to set/assert cfg.Gateway.IntrospectionAuthentication instead of top-level field.
CLI defaults
cmd/root.go
Removed default for introspection-authentication; added default for gateway-introspection-authentication; minor reordering of gateway defaults.
Integration tests update
tests/gateway_test/suite_test.go
Moved assignment of introspection auth to suite.appCfg.Gateway.IntrospectionAuthentication instead of top-level.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary feature introduced—introspection authentication—and avoids unnecessary noise or vague phrasing, making it clear and directly aligned with the PR’s main objective.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch config/IntrospectionAuthentication

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

if !rt.appCfg.Gateway.ShouldImpersonate {
rt.log.Debug().Str("path", req.URL.Path).Msg("Using bearer token authentication")

return rt.adminRT.RoundTrip(req)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used adminRT here and it worked with any token passed since adminRT works without tokens.

@vertex451 vertex451 marked this pull request as draft October 3, 2025 14:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
gateway/manager/roundtripper/roundtripper.go (1)

121-144: Verify discovery request path handling is exhaustive.

The updated isDiscoveryRequest logic handles path prefixes (/services/.../clusters/... and /clusters/...) before checking discovery endpoints. The logic looks correct, but consider the following edge cases:

  1. Paths with trailing slashes are trimmed (line 121), which is good
  2. Empty paths return false (lines 122-124), which is correct
  3. Prefix stripping logic (lines 127-131) correctly removes 5 parts for services prefix and 3 parts for clusters prefix

However, ensure that:

  • Virtual workspace paths (/services/{service}/clusters/{workspace}/api) are correctly handled
  • KCP workspace paths (/clusters/{workspace}/api) are correctly handled
  • All discovery endpoints (/api, /apis, /api/v1, /apis/group, /apis/group/v1) are recognized

Consider adding test cases for virtual workspace and KCP workspace discovery paths to ensure the prefix stripping logic works correctly:

// Test cases to add to roundtripper_test.go
{
    name:        "virtual_workspace_with_trailing_components",
    path:        "/services/myservice/clusters/workspace1/api/v1",
    isDiscovery: true,
},
{
    name:        "kcp_workspace_with_version",
    path:        "/clusters/workspace1/api/v1",
    isDiscovery: true,
},
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86cb1d4 and e85d7bf.

📒 Files selected for processing (6)
  • gateway/manager/manager.go (1 hunks)
  • gateway/manager/roundtripper/roundtripper.go (4 hunks)
  • gateway/manager/roundtripper/roundtripper_test.go (8 hunks)
  • gateway/manager/targetcluster/cluster.go (5 hunks)
  • gateway/manager/targetcluster/registry.go (2 hunks)
  • gateway/manager/targetcluster/registry_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
gateway/manager/roundtripper/roundtripper_test.go (1)
gateway/manager/roundtripper/roundtripper.go (1)
  • New (26-34)
gateway/manager/manager.go (1)
gateway/manager/targetcluster/registry.go (1)
  • NewClusterRegistry (34-43)
gateway/manager/targetcluster/cluster.go (2)
common/config/config.go (1)
  • Config (3-36)
gateway/manager/roundtripper/roundtripper.go (2)
  • New (26-34)
  • NewUnauthorizedRoundTripper (37-39)
gateway/manager/roundtripper/roundtripper.go (1)
common/config/config.go (1)
  • Config (3-36)
gateway/manager/targetcluster/registry.go (1)
gateway/manager/targetcluster/cluster.go (2)
  • TargetCluster (52-60)
  • NewTargetCluster (63-96)
gateway/manager/targetcluster/registry_test.go (1)
gateway/manager/targetcluster/registry.go (1)
  • NewClusterRegistry (34-43)
⏰ 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). (3)
  • GitHub Check: pipe / testSource / test
  • GitHub Check: pipe / dockerBuild / docker
  • GitHub Check: Analyze (go)
🔇 Additional comments (16)
gateway/manager/targetcluster/registry_test.go (1)

21-21: LGTM!

The test correctly uses the updated NewClusterRegistry signature, removing the third nil argument that previously represented the RoundTripperFactory.

gateway/manager/manager.go (1)

25-25: LGTM!

The NewClusterRegistry call correctly uses the simplified signature without the RoundTripperFactory parameter.

gateway/manager/roundtripper/roundtripper_test.go (3)

80-80: LGTM!

All test cases correctly updated to use the new roundtripper.New signature with the additional baseRoundTripper parameter. Passing mockAdmin as both adminRoundTripper and baseRoundTripper is appropriate for these test scenarios.

Also applies to: 265-265, 379-379, 454-454, 503-503, 534-534


514-514: LGTM!

Skipping this test is appropriate since it would require mocking baseRT, which is now an internal implementation detail.


557-563: Add assertion verifying admin Authorization header
The test removes the original Authorization header and sets Impersonate-User but doesn’t confirm the adminRT token is applied. Add an assertion, for example:

assert.Equal(t, "Bearer <admin-token>", capturedRequest.Header.Get("Authorization"))

or update the test documentation to clarify the expected authentication flow.

gateway/manager/targetcluster/registry.go (3)

27-31: LGTM!

The ClusterRegistry struct correctly removes the roundTripperFactory field, aligning with the refactor that moves RoundTripper wiring into the cluster connection logic.


34-42: LGTM!

The NewClusterRegistry signature and initialization correctly simplified by removing the RoundTripperFactory parameter.


59-59: LGTM!

The NewTargetCluster call correctly updated to remove the roundTripperFactory argument, consistent with the new cluster creation flow.

gateway/manager/targetcluster/cluster.go (4)

18-18: LGTM!

The import of the roundtripper package is necessary for the new internal RoundTripper construction logic.


63-68: LGTM!

The function signatures correctly updated to remove the roundTripperFactory parameter, aligning with the new approach where RoundTripper construction is handled internally.

Also applies to: 81-81


117-126: LGTM!

The new Wrap implementation correctly constructs the composite RoundTripper:

  1. Derives the base transport via unwrapToBaseTransport
  2. Constructs the custom RoundTripper with adminRT, baseRT, and unauthorizedRT

This centralizes RoundTripper construction and eliminates the need for an external factory.


172-185: Verify client-go transport wrappers implement WrappedRoundTripper
Ensure every RoundTripper decorator in your client-go version (e.g. BearerAuthRoundTripper, ImpersonatingRoundTripper) defines func WrappedRoundTripper() http.RoundTripper so unwrapToBaseTransport can recurse through the chain. For vendored code, you can run:

grep -R "func .*WrappedRoundTripper" -n vendor/k8s.io/client-go/transport
gateway/manager/roundtripper/roundtripper.go (4)

9-9: LGTM!

The import of utilnet and addition of the baseRT field are necessary for the new request cloning and bearer authentication flow.

Also applies to: 20-20


26-33: LGTM!

The New constructor correctly updated to accept and store the baseRoundTripper parameter, enabling the bearer token authentication path to use the unwrapped base transport.


70-71: LGTM!

Cloning the request before modifying headers is essential to prevent unintended side effects on the original request. This is a good security practice.


73-76: Verify non-impersonation auth behavior
Non-impersonation now wraps baseRT with the user’s bearer token instead of using admin credentials. Confirm this matches the intended authentication model and add or update documentation/config comments accordingly.

@vertex451 vertex451 marked this pull request as ready for review October 9, 2025 10:13
…orm-mesh/kubernetes-graphql-gateway into config/IntrospectionAuthentication
v.SetDefault("openapi-definitions-path", "./bin/definitions")
v.SetDefault("enable-kcp", true)
v.SetDefault("local-development", false)
v.SetDefault("introspection-authentication", false)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved under the gateway section

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
gateway/manager/roundtripper/roundtripper_test.go (1)

80-80: Add a dedicated baseRoundTripper mock.

Now that New takes a real base transport, these scenarios should assert that the non-impersonation path hits the base RT instead of the admin RT. Passing mockAdmin in both slots masks that distinction, so a regression in wiring would still go green. Please introduce a separate mockBase (with its own expectations) or reuse an existing mock configured for the bearer path so the tests fail if the wrong transport is used.

Also applies to: 265-265, 379-379, 454-454, 503-503, 530-530

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e85d7bf and 2aa487a.

📒 Files selected for processing (7)
  • cmd/root.go (1 hunks)
  • common/config/config.go (2 hunks)
  • common/config/config_test.go (4 hunks)
  • gateway/manager/roundtripper/roundtripper.go (3 hunks)
  • gateway/manager/roundtripper/roundtripper_test.go (7 hunks)
  • gateway/manager/targetcluster/registry.go (3 hunks)
  • tests/gateway_test/suite_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • gateway/manager/roundtripper/roundtripper.go
🧰 Additional context used
🧬 Code graph analysis (3)
gateway/manager/targetcluster/registry.go (3)
gateway/manager/targetcluster/cluster.go (2)
  • TargetCluster (52-60)
  • NewTargetCluster (63-96)
common/config/config.go (1)
  • Config (3-36)
gateway/manager/targetcluster/graphql.go (1)
  • IsIntrospectionQuery (81-97)
gateway/manager/roundtripper/roundtripper_test.go (1)
gateway/manager/roundtripper/roundtripper.go (1)
  • New (26-34)
common/config/config_test.go (1)
gateway/schema/schema.go (1)
  • Gateway (24-37)
⏰ 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). (3)
  • GitHub Check: pipe / testSource / test
  • GitHub Check: pipe / dockerBuild / docker
  • GitHub Check: Analyze (go)
🔇 Additional comments (7)
cmd/root.go (1)

69-69: Default relocation looks good.

gateway-introspection-authentication aligns with the new nested config field.

common/config/config_test.go (1)

16-16: Tests now target the nested introspection flag correctly.

Initialization, assignment, and assertions all reference cfg.Gateway.IntrospectionAuthentication, matching the struct changes.

Also applies to: 40-42, 54-54, 68-68

tests/gateway_test/suite_test.go (1)

127-127: Suite setup tracks the relocated flag.

Propagating AuthenticateSchemaRequests via appCfg.Gateway.IntrospectionAuthentication keeps the tests aligned with the new config shape.

common/config/config.go (1)

4-36: Config struct reshaping is consistent.

Moving IntrospectionAuthentication under Gateway with gateway- mapstructure tags matches the new defaults and test expectations.

gateway/manager/targetcluster/registry.go (3)

26-31: LGTM! ClusterRegistry simplified.

The removal of the roundTripperFactory field simplifies the struct and aligns with the PR objectives.


34-43: LGTM! Signature simplified.

The removal of the roundTripperFactory parameter is consistent with the struct changes and simplifies the constructor.


59-59: LGTM! Call updated to match new signature.

The call to NewTargetCluster correctly omits the roundTripperFactory argument, matching the updated function signature.

Comment on lines 193 to 220
func (cr *ClusterRegistry) handleAuth(w http.ResponseWriter, r *http.Request, token string, cluster *TargetCluster) bool {
if !cr.appCfg.LocalDevelopment {
if cr.appCfg.LocalDevelopment {
return true
}

if cr.appCfg.Gateway.IntrospectionAuthentication {
if token == "" {
http.Error(w, "Authorization header is required", http.StatusUnauthorized)
return false
}

if cr.appCfg.IntrospectionAuthentication {
if IsIntrospectionQuery(r) {
valid, err := cr.validateToken(r.Context(), token, cluster)
if err != nil {
cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Error validating token")
http.Error(w, "Token validation failed", http.StatusUnauthorized)
return false
}
if !valid {
cr.log.Debug().Str("cluster", cluster.name).Msg("Invalid token for introspection query")
http.Error(w, "Invalid token", http.StatusUnauthorized)
return false
}
if IsIntrospectionQuery(r) {
valid, err := cr.validateToken(r.Context(), token, cluster)
if err != nil {
cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Error validating token")
http.Error(w, "Token validation failed", http.StatusUnauthorized)
return false
}
if !valid {
cr.log.Debug().Str("cluster", cluster.name).Msg("Invalid token for introspection query")
http.Error(w, "Invalid token", http.StatusUnauthorized)
return false
}
}
}

return true
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical logic flaw: token required for all requests when IntrospectionAuthentication is enabled.

The current implementation at lines 199-202 rejects all requests when IntrospectionAuthentication is true and the token is empty, regardless of whether the request is an introspection query. This contradicts the expected behavior where authentication should only be required for introspection queries when IntrospectionAuthentication is enabled.

Current flow:

  1. If IntrospectionAuthentication=true and token="" → reject all requests
  2. If IntrospectionAuthentication=true and token!="" and IsIntrospectionQuery → validate token
  3. Otherwise → allow

Expected flow:

  1. If IntrospectionAuthentication=true AND IsIntrospectionQuery → require and validate token
  2. Otherwise → allow

Non-introspection queries should not require a token when IntrospectionAuthentication is enabled.

Apply this diff to fix the authentication logic:

 func (cr *ClusterRegistry) handleAuth(w http.ResponseWriter, r *http.Request, token string, cluster *TargetCluster) bool {
 	if cr.appCfg.LocalDevelopment {
 		return true
 	}
 
-	if cr.appCfg.Gateway.IntrospectionAuthentication {
+	if cr.appCfg.Gateway.IntrospectionAuthentication && IsIntrospectionQuery(r) {
 		if token == "" {
 			http.Error(w, "Authorization header is required", http.StatusUnauthorized)
 			return false
 		}
 
-		if IsIntrospectionQuery(r) {
-			valid, err := cr.validateToken(r.Context(), token, cluster)
-			if err != nil {
-				cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Error validating token")
-				http.Error(w, "Token validation failed", http.StatusUnauthorized)
-				return false
-			}
-			if !valid {
-				cr.log.Debug().Str("cluster", cluster.name).Msg("Invalid token for introspection query")
-				http.Error(w, "Invalid token", http.StatusUnauthorized)
-				return false
-			}
+		valid, err := cr.validateToken(r.Context(), token, cluster)
+		if err != nil {
+			cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Error validating token")
+			http.Error(w, "Token validation failed", http.StatusUnauthorized)
+			return false
+		}
+		if !valid {
+			cr.log.Debug().Str("cluster", cluster.name).Msg("Invalid token for introspection query")
+			http.Error(w, "Invalid token", http.StatusUnauthorized)
+			return false
 		}
 	}
 
 	return true
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (cr *ClusterRegistry) handleAuth(w http.ResponseWriter, r *http.Request, token string, cluster *TargetCluster) bool {
if !cr.appCfg.LocalDevelopment {
if cr.appCfg.LocalDevelopment {
return true
}
if cr.appCfg.Gateway.IntrospectionAuthentication {
if token == "" {
http.Error(w, "Authorization header is required", http.StatusUnauthorized)
return false
}
if cr.appCfg.IntrospectionAuthentication {
if IsIntrospectionQuery(r) {
valid, err := cr.validateToken(r.Context(), token, cluster)
if err != nil {
cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Error validating token")
http.Error(w, "Token validation failed", http.StatusUnauthorized)
return false
}
if !valid {
cr.log.Debug().Str("cluster", cluster.name).Msg("Invalid token for introspection query")
http.Error(w, "Invalid token", http.StatusUnauthorized)
return false
}
if IsIntrospectionQuery(r) {
valid, err := cr.validateToken(r.Context(), token, cluster)
if err != nil {
cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Error validating token")
http.Error(w, "Token validation failed", http.StatusUnauthorized)
return false
}
if !valid {
cr.log.Debug().Str("cluster", cluster.name).Msg("Invalid token for introspection query")
http.Error(w, "Invalid token", http.StatusUnauthorized)
return false
}
}
}
return true
}
func (cr *ClusterRegistry) handleAuth(w http.ResponseWriter, r *http.Request, token string, cluster *TargetCluster) bool {
if cr.appCfg.LocalDevelopment {
return true
}
if cr.appCfg.Gateway.IntrospectionAuthentication && IsIntrospectionQuery(r) {
if token == "" {
http.Error(w, "Authorization header is required", http.StatusUnauthorized)
return false
}
valid, err := cr.validateToken(r.Context(), token, cluster)
if err != nil {
cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Error validating token")
http.Error(w, "Token validation failed", http.StatusUnauthorized)
return false
}
if !valid {
cr.log.Debug().Str("cluster", cluster.name).Msg("Invalid token for introspection query")
http.Error(w, "Invalid token", http.StatusUnauthorized)
return false
}
}
return true
}
🤖 Prompt for AI Agents
In gateway/manager/targetcluster/registry.go around lines 193 to 220, the
current logic incorrectly requires a token for all requests whenever
IntrospectionAuthentication is enabled; change the flow so token presence and
validation are only enforced for introspection queries: first check if
IntrospectionAuthentication is true AND IsIntrospectionQuery(r) is true, and
only then require token (return 401 if empty) and call cr.validateToken, logging
and returning on errors or invalid tokens; for all other cases
(non-introspection queries or when IntrospectionAuthentication is false) return
true.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant