diff --git a/cmd/root.go b/cmd/root.go index cad4a4a..3eace20 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,8 +31,6 @@ func init() { } cobra.OnInitialize(func() { - initConfig() - var err error log, err = setupLogger(defaultCfg.Log.Level) if err != nil { @@ -51,36 +49,6 @@ func init() { } } -func initConfig() { - // Top-level defaults - v.SetDefault("openapi-definitions-path", "./bin/definitions") - v.SetDefault("enable-kcp", true) - v.SetDefault("local-development", false) - v.SetDefault("introspection-authentication", false) - - // Listener - v.SetDefault("listener-apiexport-workspace", ":root") - v.SetDefault("listener-apiexport-name", "kcp.io") - - // Gateway - v.SetDefault("gateway-port", "8080") - - v.SetDefault("gateway-username-claim", "email") - v.SetDefault("gateway-should-impersonate", true) - // Gateway Handler config - v.SetDefault("gateway-handler-pretty", true) - v.SetDefault("gateway-handler-playground", true) - v.SetDefault("gateway-handler-graphiql", true) - // Gateway CORS - v.SetDefault("gateway-cors-enabled", false) - v.SetDefault("gateway-cors-allowed-origins", "*") - v.SetDefault("gateway-cors-allowed-headers", "*") - // Gateway URL - v.SetDefault("gateway-url-virtual-workspace-prefix", "virtual-workspace") - v.SetDefault("gateway-url-default-kcp-workspace", "root") - v.SetDefault("gateway-url-graphql-suffix", "graphql") -} - // setupLogger initializes the logger with the given log level func setupLogger(logLevel string) (*logger.Logger, error) { loggerCfg := logger.DefaultConfig() diff --git a/common/config/config.go b/common/config/config.go index 8e46a94..2c42a04 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -1,15 +1,14 @@ package config type Config struct { - OpenApiDefinitionsPath string `mapstructure:"openapi-definitions-path"` - EnableKcp bool `mapstructure:"enable-kcp"` - LocalDevelopment bool `mapstructure:"local-development"` - IntrospectionAuthentication bool `mapstructure:"introspection-authentication"` + OpenApiDefinitionsPath string `mapstructure:"openapi-definitions-path" default:"./bin/definitions"` + EnableKcp bool `mapstructure:"enable-kcp" default:"true"` + LocalDevelopment bool `mapstructure:"local-development" default:"false"` Url struct { - VirtualWorkspacePrefix string `mapstructure:"gateway-url-virtual-workspace-prefix"` - DefaultKcpWorkspace string `mapstructure:"gateway-url-default-kcp-workspace"` - GraphqlSuffix string `mapstructure:"gateway-url-graphql-suffix"` + VirtualWorkspacePrefix string `mapstructure:"gateway-url-virtual-workspace-prefix" default:"virtual-workspace"` + DefaultKcpWorkspace string `mapstructure:"gateway-url-default-kcp-workspace" default:"root"` + GraphqlSuffix string `mapstructure:"gateway-url-graphql-suffix" default:"graphql"` } `mapstructure:",squash"` Listener struct { @@ -17,20 +16,21 @@ type Config struct { } `mapstructure:",squash"` Gateway struct { - Port string `mapstructure:"gateway-port"` - UsernameClaim string `mapstructure:"gateway-username-claim"` - ShouldImpersonate bool `mapstructure:"gateway-should-impersonate"` + Port string `mapstructure:"gateway-port" default:"8080"` + UsernameClaim string `mapstructure:"gateway-username-claim" default:"email"` + ShouldImpersonate bool `mapstructure:"gateway-should-impersonate" default:"true"` + IntrospectionAuthentication bool `mapstructure:"gateway-introspection-authentication" default:"false"` HandlerCfg struct { - Pretty bool `mapstructure:"gateway-handler-pretty"` - Playground bool `mapstructure:"gateway-handler-playground"` - GraphiQL bool `mapstructure:"gateway-handler-graphiql"` + Pretty bool `mapstructure:"gateway-handler-pretty" default:"true"` + Playground bool `mapstructure:"gateway-handler-playground" default:"true"` + GraphiQL bool `mapstructure:"gateway-handler-graphiql" default:"true"` } `mapstructure:",squash"` Cors struct { - Enabled bool `mapstructure:"gateway-cors-enabled"` - AllowedOrigins string `mapstructure:"gateway-cors-allowed-origins"` - AllowedHeaders string `mapstructure:"gateway-cors-allowed-headers"` + Enabled bool `mapstructure:"gateway-cors-enabled" default:"false"` + AllowedOrigins string `mapstructure:"gateway-cors-allowed-origins" default:"*"` + AllowedHeaders string `mapstructure:"gateway-cors-allowed-headers" default:"*"` } `mapstructure:",squash"` } `mapstructure:",squash"` } diff --git a/common/config/config_test.go b/common/config/config_test.go index 6f5d7c8..c1b3e3e 100644 --- a/common/config/config_test.go +++ b/common/config/config_test.go @@ -13,7 +13,7 @@ func TestConfig_StructInitialization(t *testing.T) { assert.Empty(t, cfg.OpenApiDefinitionsPath) assert.False(t, cfg.EnableKcp) assert.False(t, cfg.LocalDevelopment) - assert.False(t, cfg.IntrospectionAuthentication) + assert.False(t, cfg.Gateway.IntrospectionAuthentication) // Test nested struct fields assert.Empty(t, cfg.Url.VirtualWorkspacePrefix) @@ -37,10 +37,9 @@ func TestConfig_StructInitialization(t *testing.T) { func TestConfig_FieldAssignment(t *testing.T) { cfg := Config{ - OpenApiDefinitionsPath: "/path/to/definitions", - EnableKcp: true, - LocalDevelopment: true, - IntrospectionAuthentication: true, + OpenApiDefinitionsPath: "/path/to/definitions", + EnableKcp: true, + LocalDevelopment: true, } cfg.Url.VirtualWorkspacePrefix = "workspace" @@ -52,6 +51,7 @@ func TestConfig_FieldAssignment(t *testing.T) { cfg.Gateway.Port = "8080" cfg.Gateway.UsernameClaim = "email" cfg.Gateway.ShouldImpersonate = true + cfg.Gateway.IntrospectionAuthentication = true cfg.Gateway.HandlerCfg.Pretty = true cfg.Gateway.HandlerCfg.Playground = true @@ -65,7 +65,7 @@ func TestConfig_FieldAssignment(t *testing.T) { assert.Equal(t, "/path/to/definitions", cfg.OpenApiDefinitionsPath) assert.True(t, cfg.EnableKcp) assert.True(t, cfg.LocalDevelopment) - assert.True(t, cfg.IntrospectionAuthentication) + assert.True(t, cfg.Gateway.IntrospectionAuthentication) assert.Equal(t, "workspace", cfg.Url.VirtualWorkspacePrefix) assert.Equal(t, "default", cfg.Url.DefaultKcpWorkspace) @@ -89,16 +89,9 @@ func TestConfig_FieldAssignment(t *testing.T) { func TestConfig_NestedStructModification(t *testing.T) { cfg := Config{} - // Test direct modification of nested structs - cfg.Gateway.HandlerCfg = struct { - Pretty bool `mapstructure:"gateway-handler-pretty"` - Playground bool `mapstructure:"gateway-handler-playground"` - GraphiQL bool `mapstructure:"gateway-handler-graphiql"` - }{ - Pretty: true, - Playground: false, - GraphiQL: true, - } + cfg.Gateway.HandlerCfg.Pretty = true + cfg.Gateway.HandlerCfg.Playground = false + cfg.Gateway.HandlerCfg.GraphiQL = true assert.True(t, cfg.Gateway.HandlerCfg.Pretty) assert.False(t, cfg.Gateway.HandlerCfg.Playground) diff --git a/gateway/manager/manager.go b/gateway/manager/manager.go index ab6d358..0e227b7 100644 --- a/gateway/manager/manager.go +++ b/gateway/manager/manager.go @@ -7,10 +7,8 @@ import ( "github.com/pkg/errors" "github.com/platform-mesh/golang-commons/logger" - "k8s.io/client-go/rest" appConfig "github.com/platform-mesh/kubernetes-graphql-gateway/common/config" - "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/manager/roundtripper" "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/manager/targetcluster" "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/manager/watcher" ) @@ -24,12 +22,7 @@ type Service struct { // NewGateway creates a new domain-driven Gateway instance func NewGateway(ctx context.Context, log *logger.Logger, appCfg appConfig.Config) (*Service, error) { - // Create round tripper factory - roundTripperFactory := targetcluster.RoundTripperFactory(func(adminRT http.RoundTripper, tlsConfig rest.TLSClientConfig) http.RoundTripper { - return roundtripper.New(log, appCfg, adminRT, roundtripper.NewUnauthorizedRoundTripper()) - }) - - clusterRegistry := targetcluster.NewClusterRegistry(log, appCfg, roundTripperFactory) + clusterRegistry := targetcluster.NewClusterRegistry(log, appCfg) schemaWatcher, err := watcher.NewFileWatcher(log, clusterRegistry) if err != nil { diff --git a/gateway/manager/roundtripper/roundtripper.go b/gateway/manager/roundtripper/roundtripper.go index ec5fdd3..1418818 100644 --- a/gateway/manager/roundtripper/roundtripper.go +++ b/gateway/manager/roundtripper/roundtripper.go @@ -6,6 +6,8 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/platform-mesh/golang-commons/logger" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/rest" "k8s.io/client-go/transport" "github.com/platform-mesh/kubernetes-graphql-gateway/common/config" @@ -16,15 +18,17 @@ type TokenKey struct{} type roundTripper struct { log *logger.Logger adminRT, unauthorizedRT http.RoundTripper + baseRT http.RoundTripper appCfg config.Config } type unauthorizedRoundTripper struct{} -func New(log *logger.Logger, appCfg config.Config, adminRoundTripper, unauthorizedRT http.RoundTripper) http.RoundTripper { +func New(log *logger.Logger, appCfg config.Config, adminRoundTripper, baseRoundTripper, unauthorizedRT http.RoundTripper) http.RoundTripper { return &roundTripper{ log: log, adminRT: adminRoundTripper, + baseRT: baseRoundTripper, unauthorizedRT: unauthorizedRT, appCfg: appCfg, } @@ -35,6 +39,18 @@ func NewUnauthorizedRoundTripper() http.RoundTripper { return &unauthorizedRoundTripper{} } +// NewBaseRoundTripper creates a base HTTP transport with only TLS configuration (no authentication) +func NewBaseRoundTripper(tlsConfig rest.TLSClientConfig) (http.RoundTripper, error) { + return rest.TransportFor(&rest.Config{ + TLSClientConfig: rest.TLSClientConfig{ + Insecure: tlsConfig.Insecure, + ServerName: tlsConfig.ServerName, + CAFile: tlsConfig.CAFile, + CAData: tlsConfig.CAData, + }, + }) +} + func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { rt.log.Info(). Str("req.Host", req.Host). @@ -65,13 +81,12 @@ func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { } // No we are going to use token based auth only, so we are reassigning the headers + req = utilnet.CloneRequest(req) req.Header.Del("Authorization") - req.Header.Set("Authorization", "Bearer "+token) if !rt.appCfg.Gateway.ShouldImpersonate { rt.log.Debug().Str("path", req.URL.Path).Msg("Using bearer token authentication") - - return rt.adminRT.RoundTrip(req) + return transport.NewBearerAuthRoundTripper(token, rt.baseRT).RoundTrip(req) } // Impersonation mode: extract user from token and impersonate diff --git a/gateway/manager/roundtripper/roundtripper_test.go b/gateway/manager/roundtripper/roundtripper_test.go index 9c449bc..9063ded 100644 --- a/gateway/manager/roundtripper/roundtripper_test.go +++ b/gateway/manager/roundtripper/roundtripper_test.go @@ -77,7 +77,7 @@ func TestRoundTripper_RoundTrip(t *testing.T) { appCfg.Gateway.ShouldImpersonate = tt.shouldImpersonate appCfg.Gateway.UsernameClaim = "sub" - rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockUnauthorized) + rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockAdmin, mockUnauthorized) req := httptest.NewRequest(http.MethodGet, "http://example.com/api/v1/pods", nil) if tt.token != "" { @@ -262,7 +262,7 @@ func TestRoundTripper_DiscoveryRequests(t *testing.T) { appCfg.Gateway.ShouldImpersonate = false appCfg.Gateway.UsernameClaim = "sub" - rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockUnauthorized) + rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockAdmin, mockUnauthorized) req := httptest.NewRequest(tt.method, "http://example.com"+tt.path, nil) @@ -376,7 +376,7 @@ func TestRoundTripper_ComprehensiveFunctionality(t *testing.T) { appCfg.Gateway.ShouldImpersonate = tt.shouldImpersonate appCfg.Gateway.UsernameClaim = tt.usernameClaim - rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockUnauthorized) + rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockAdmin, mockUnauthorized) req := httptest.NewRequest(http.MethodGet, "http://example.com/api/v1/pods", nil) if tt.token != "" { @@ -451,7 +451,7 @@ func TestRoundTripper_KCPDiscoveryRequests(t *testing.T) { appCfg.Gateway.ShouldImpersonate = false appCfg.Gateway.UsernameClaim = "sub" - rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockUnauthorized) + rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockAdmin, mockUnauthorized) req := httptest.NewRequest(http.MethodGet, "http://example.com"+tt.path, nil) @@ -500,7 +500,7 @@ func TestRoundTripper_InvalidTokenSecurityFix(t *testing.T) { appCfg.Gateway.ShouldImpersonate = false appCfg.Gateway.UsernameClaim = "sub" - rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockUnauthorized) + rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockAdmin, mockUnauthorized) req := httptest.NewRequest(http.MethodGet, "/api/v1/pods", nil) // Don't set a token to simulate the invalid token case @@ -515,11 +515,12 @@ func TestRoundTripper_ExistingAuthHeadersAreCleanedBeforeTokenAuth(t *testing.T) // before setting the bearer token, preventing admin credentials from leaking through mockAdmin := &mocks.MockRoundTripper{} + mockBase := &mocks.MockRoundTripper{} mockUnauthorized := &mocks.MockRoundTripper{} // Capture the request that gets sent to adminRT var capturedRequest *http.Request - mockAdmin.EXPECT().RoundTrip(mock.Anything).Return(&http.Response{StatusCode: http.StatusOK}, nil).Run(func(req *http.Request) { + mockBase.EXPECT().RoundTrip(mock.Anything).Return(&http.Response{StatusCode: http.StatusOK}, nil).Run(func(req *http.Request) { capturedRequest = req }) @@ -527,7 +528,7 @@ func TestRoundTripper_ExistingAuthHeadersAreCleanedBeforeTokenAuth(t *testing.T) appCfg.Gateway.ShouldImpersonate = false appCfg.Gateway.UsernameClaim = "sub" - rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockUnauthorized) + rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockBase, mockUnauthorized) req := httptest.NewRequest(http.MethodGet, "/api/v1/pods", nil) @@ -555,6 +556,7 @@ func TestRoundTripper_ExistingAuthHeadersAreCleanedBeforeImpersonation(t *testin // before setting the bearer token in impersonation mode mockAdmin := &mocks.MockRoundTripper{} + mockBase := &mocks.MockRoundTripper{} mockUnauthorized := &mocks.MockRoundTripper{} // Capture the request that gets sent to the impersonation round tripper (which uses adminRT) @@ -567,7 +569,7 @@ func TestRoundTripper_ExistingAuthHeadersAreCleanedBeforeImpersonation(t *testin appCfg.Gateway.ShouldImpersonate = true appCfg.Gateway.UsernameClaim = "sub" - rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockUnauthorized) + rt := roundtripper.New(testlogger.New().Logger, appCfg, mockAdmin, mockBase, mockUnauthorized) req := httptest.NewRequest(http.MethodGet, "/api/v1/pods", nil) @@ -591,7 +593,6 @@ func TestRoundTripper_ExistingAuthHeadersAreCleanedBeforeImpersonation(t *testin // Verify that the captured request has the correct Authorization header require.NotNil(t, capturedRequest) authHeader := capturedRequest.Header.Get("Authorization") - assert.Equal(t, "Bearer "+tokenString, authHeader) // Verify that the original admin token was removed assert.NotContains(t, authHeader, "admin-token-that-should-be-removed") diff --git a/gateway/manager/targetcluster/cluster.go b/gateway/manager/targetcluster/cluster.go index 612b279..61a1b99 100644 --- a/gateway/manager/targetcluster/cluster.go +++ b/gateway/manager/targetcluster/cluster.go @@ -1,6 +1,7 @@ package targetcluster import ( + "context" "encoding/json" "fmt" "net/http" @@ -9,14 +10,16 @@ import ( "github.com/go-openapi/spec" "github.com/platform-mesh/golang-commons/logger" + corev1 "k8s.io/api/core/v1" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/kcp" "github.com/platform-mesh/kubernetes-graphql-gateway/common/auth" appConfig "github.com/platform-mesh/kubernetes-graphql-gateway/common/config" + "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/manager/roundtripper" "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/resolver" - "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/schema" + gwschema "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/schema" ) // FileData represents the data extracted from a schema file @@ -64,7 +67,6 @@ func NewTargetCluster( schemaFilePath string, log *logger.Logger, appCfg appConfig.Config, - roundTripperFactory func(http.RoundTripper, rest.TLSClientConfig) http.RoundTripper, ) (*TargetCluster, error) { fileData, err := readSchemaFile(schemaFilePath) if err != nil { @@ -78,7 +80,7 @@ func NewTargetCluster( } // Connect to cluster - use metadata if available, otherwise fall back to standard config - if err := cluster.connect(appCfg, fileData.ClusterMetadata, roundTripperFactory); err != nil { + if err := cluster.connect(appCfg, fileData.ClusterMetadata); err != nil { return nil, fmt.Errorf("failed to connect to cluster: %w", err) } @@ -96,7 +98,7 @@ func NewTargetCluster( } // connect establishes connection to the target cluster -func (tc *TargetCluster) connect(appCfg appConfig.Config, metadata *ClusterMetadata, roundTripperFactory func(http.RoundTripper, rest.TLSClientConfig) http.RoundTripper) error { +func (tc *TargetCluster) connect(appCfg appConfig.Config, metadata *ClusterMetadata) error { // All clusters now use metadata from schema files to get kubeconfig if metadata == nil { return fmt.Errorf("cluster %s requires cluster metadata in schema file", tc.name) @@ -114,12 +116,21 @@ func (tc *TargetCluster) connect(appCfg appConfig.Config, metadata *ClusterMetad return fmt.Errorf("failed to build config from metadata: %w", err) } - if roundTripperFactory != nil { - tc.restCfg.Wrap(func(rt http.RoundTripper) http.RoundTripper { - return roundTripperFactory(rt, tc.restCfg.TLSClientConfig) - }) + baseRT, err := roundtripper.NewBaseRoundTripper(tc.restCfg.TLSClientConfig) + if err != nil { + return fmt.Errorf("failed to create base transport: %w", err) } + tc.restCfg.Wrap(func(adminRT http.RoundTripper) http.RoundTripper { + return roundtripper.New( + tc.log, + tc.appCfg, + adminRT, + baseRT, + roundtripper.NewUnauthorizedRoundTripper(), + ) + }) + // Create client - use KCP-aware client only for KCP mode, standard client otherwise if appCfg.EnableKcp { tc.client, err = kcp.NewClusterAwareClientWithWatch(tc.restCfg, client.Options{}) @@ -176,7 +187,7 @@ func (tc *TargetCluster) createHandler(definitions map[string]interface{}, appCf resolverProvider := resolver.New(tc.log, tc.client) // Create schema gateway - schemaGateway, err := schema.New(tc.log, specDefs, resolverProvider) + schemaGateway, err := gwschema.New(tc.log, specDefs, resolverProvider) if err != nil { return fmt.Errorf("failed to create GraphQL schema: %w", err) } @@ -216,6 +227,33 @@ func (tc *TargetCluster) GetEndpoint(appCfg appConfig.Config) string { return fmt.Sprintf("/%s/%s", path, appCfg.Url.GraphqlSuffix) } +func (tc *TargetCluster) ValidateToken(ctx context.Context, token string) (bool, error) { + newCtx := context.WithValue(ctx, roundtripper.TokenKey{}, token) + + configMapList := &corev1.ConfigMapList{} + listOpts := &client.ListOptions{ + Limit: 1, + } + + err := tc.client.List(newCtx, configMapList, listOpts) + if err != nil { + errStrLower := strings.ToLower(err.Error()) + if strings.Contains(errStrLower, "unauthorized") || strings.Contains(errStrLower, "401") { + tc.log.Debug().Err(err).Str("cluster", tc.name).Msg("Token is invalid - unauthorized") + return false, nil + } + if strings.Contains(errStrLower, "forbidden") || strings.Contains(errStrLower, "403") || strings.Contains(errStrLower, "access denied") { + tc.log.Debug().Str("cluster", tc.name).Msg("Token is valid but user has no permission to list configmaps") + return true, nil + } + tc.log.Error().Err(err).Str("cluster", tc.name).Msg("Unexpected error during token validation") + return false, err + } + + tc.log.Debug().Str("cluster", tc.name).Msg("Token validated successfully") + return true, nil +} + // ServeHTTP handles HTTP requests for this cluster func (tc *TargetCluster) ServeHTTP(w http.ResponseWriter, r *http.Request) { if tc.handler == nil || tc.handler.Handler == nil { diff --git a/gateway/manager/targetcluster/export_test.go b/gateway/manager/targetcluster/export_test.go index 21ea083..e01c4cd 100644 --- a/gateway/manager/targetcluster/export_test.go +++ b/gateway/manager/targetcluster/export_test.go @@ -1,6 +1,8 @@ package targetcluster import ( + "net/http" + "github.com/platform-mesh/golang-commons/logger" "k8s.io/client-go/rest" @@ -19,6 +21,10 @@ func NewTestTargetCluster(name string) *TargetCluster { } } +func IsIntrospectionQuery(r *http.Request) bool { + return isIntrospectionQuery(r) +} + // CreateTestConfig creates an appConfig.Config for testing with the specified settings func CreateTestConfig(localDev bool, gatewayPort string) appConfig.Config { config := appConfig.Config{ diff --git a/gateway/manager/targetcluster/graphql.go b/gateway/manager/targetcluster/graphql.go index a0f63ac..bf20606 100644 --- a/gateway/manager/targetcluster/graphql.go +++ b/gateway/manager/targetcluster/graphql.go @@ -1,11 +1,9 @@ package targetcluster import ( - "bytes" "context" "encoding/json" "fmt" - "io" "net/http" "strings" @@ -77,25 +75,6 @@ func GetToken(r *http.Request) string { return token } -// IsIntrospectionQuery checks if the request contains a GraphQL introspection query -func IsIntrospectionQuery(r *http.Request) bool { - var params struct { - Query string `json:"query"` - } - bodyBytes, err := io.ReadAll(r.Body) - r.Body.Close() - if err == nil { - if err = json.Unmarshal(bodyBytes, ¶ms); err == nil { - if strings.Contains(params.Query, "__schema") || strings.Contains(params.Query, "__type") { - r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - return true - } - } - } - r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - return false -} - // HandleSubscription handles GraphQL subscription requests using Server-Sent Events func (s *GraphQLServer) HandleSubscription(w http.ResponseWriter, r *http.Request, schema *graphql.Schema) { // Set SSE headers diff --git a/gateway/manager/targetcluster/registry.go b/gateway/manager/targetcluster/registry.go index 26e3a56..061c18e 100644 --- a/gateway/manager/targetcluster/registry.go +++ b/gateway/manager/targetcluster/registry.go @@ -1,19 +1,18 @@ package targetcluster import ( + "bytes" "context" - "errors" + "encoding/json" "fmt" + "io" "net/http" - "net/url" "path/filepath" "strings" "sync" "github.com/platform-mesh/golang-commons/logger" appConfig "github.com/platform-mesh/kubernetes-graphql-gateway/common/config" - "github.com/platform-mesh/kubernetes-graphql-gateway/gateway/manager/roundtripper" - "k8s.io/client-go/rest" ) // contextKey is a custom type for context keys to avoid collisions @@ -22,29 +21,23 @@ type contextKey string // kcpWorkspaceKey is the context key for storing KCP workspace information const kcpWorkspaceKey contextKey = "kcpWorkspace" -// RoundTripperFactory creates HTTP round trippers for authentication -type RoundTripperFactory func(http.RoundTripper, rest.TLSClientConfig) http.RoundTripper - // ClusterRegistry manages multiple target clusters and handles HTTP routing to them type ClusterRegistry struct { - mu sync.RWMutex - clusters map[string]*TargetCluster - log *logger.Logger - appCfg appConfig.Config - roundTripperFactory RoundTripperFactory + mu sync.RWMutex + clusters map[string]*TargetCluster + log *logger.Logger + appCfg appConfig.Config } // NewClusterRegistry creates a new cluster registry func NewClusterRegistry( log *logger.Logger, appCfg appConfig.Config, - roundTripperFactory RoundTripperFactory, ) *ClusterRegistry { return &ClusterRegistry{ - clusters: make(map[string]*TargetCluster), - log: log, - appCfg: appCfg, - roundTripperFactory: roundTripperFactory, + clusters: make(map[string]*TargetCluster), + log: log, + appCfg: appCfg, } } @@ -62,7 +55,7 @@ func (cr *ClusterRegistry) LoadCluster(schemaFilePath string) error { Msg("Loading target cluster") // Create or update cluster - cluster, err := NewTargetCluster(name, schemaFilePath, cr.log, cr.appCfg, cr.roundTripperFactory) + cluster, err := NewTargetCluster(name, schemaFilePath, cr.log, cr.appCfg) if err != nil { return fmt.Errorf("failed to create target cluster %s: %w", name, err) } @@ -171,13 +164,14 @@ func (cr *ClusterRegistry) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Extract and validate token for non-GET requests token := GetToken(r) - if !cr.handleAuth(w, r, token, cluster) { - return - } // Set contexts for KCP and authentication r = SetContexts(r, clusterName, token, cr.appCfg.EnableKcp) + if !cr.handleAuth(w, r, token, cluster) { + return + } + // Handle subscription requests if r.Header.Get("Accept") == "text/event-stream" { // Subscriptions will be handled by the cluster's ServeHTTP method @@ -197,31 +191,61 @@ func (cr *ClusterRegistry) ServeHTTP(w http.ResponseWriter, r *http.Request) { // handleAuth handles authentication for non-GET requests func (cr *ClusterRegistry) handleAuth(w http.ResponseWriter, r *http.Request, token string, cluster *TargetCluster) bool { - if !cr.appCfg.LocalDevelopment { - if token == "" { - http.Error(w, "Authorization header is required", http.StatusUnauthorized) - return false - } + if cr.appCfg.LocalDevelopment { + return true + } + + if isIntrospectionQuery(r) { + if cr.appCfg.Gateway.IntrospectionAuthentication { + if token == "" { + http.Error(w, "Authorization header is required for introspection queries", 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 - } + valid, err := cluster.ValidateToken(r.Context(), token) + if err != nil { + cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Token validation failed") + 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 } + + if token == "" { + http.Error(w, "Authorization header is required", http.StatusUnauthorized) + return false + } + return true } +// isIntrospectionQuery checks if the request contains a GraphQL introspection query +func isIntrospectionQuery(r *http.Request) bool { + var params struct { + Query string `json:"query"` + } + bodyBytes, err := io.ReadAll(r.Body) + r.Body.Close() + if err == nil { + if err = json.Unmarshal(bodyBytes, ¶ms); err == nil { + if strings.Contains(params.Query, "__schema") || strings.Contains(params.Query, "__type") { + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + return true + } + } + } + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + return false +} + // handleCORS handles CORS preflight requests and headers func (cr *ClusterRegistry) handleCORS(w http.ResponseWriter, r *http.Request) bool { if cr.appCfg.Gateway.Cors.Enabled { @@ -236,79 +260,6 @@ func (cr *ClusterRegistry) handleCORS(w http.ResponseWriter, r *http.Request) bo return false } -func (cr *ClusterRegistry) validateToken(ctx context.Context, token string, cluster *TargetCluster) (bool, error) { - if cluster == nil { - return false, errors.New("no cluster provided to validate token") - } - - cr.log.Debug().Str("cluster", cluster.name).Msg("Validating token for introspection query") - - // Get the cluster's config - clusterConfig := cluster.GetConfig() - if clusterConfig == nil { - return false, fmt.Errorf("cluster %s has no config", cluster.name) - } - - cr.log.Debug(). - Str("cluster", cluster.name). - Str("host", clusterConfig.Host). - Bool("insecure", clusterConfig.TLSClientConfig.Insecure). - Bool("has_ca_data", len(clusterConfig.TLSClientConfig.CAData) > 0). - Bool("has_bearer_token", clusterConfig.BearerToken != ""). - Str("provided_token", token). - Msg("Cluster configuration for token validation") - - // Create HTTP client using the cluster's existing config and roundtripper - // This ensures we use the same authentication flow as normal requests - httpClient, err := rest.HTTPClientFor(clusterConfig) - if err != nil { - return false, fmt.Errorf("failed to create HTTP client: %w", err) - } - - // Use namespaces endpoint for token validation - it's a resource endpoint (not discovery) - // so it will use the token authentication instead of being routed to admin credentials - apiURL, err := url.JoinPath(clusterConfig.Host, "/api/v1/namespaces") - if err != nil { - return false, fmt.Errorf("failed to construct API URL: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) - if err != nil { - return false, fmt.Errorf("failed to create request: %w", err) - } - - // Set the token in the request context so the roundtripper can use it - // This leverages the same authentication logic as normal requests - req = req.WithContext(context.WithValue(req.Context(), roundtripper.TokenKey{}, token)) - - cr.log.Debug().Str("cluster", cluster.name).Str("url", apiURL).Msg("Making token validation request") - - resp, err := httpClient.Do(req) - if err != nil { - cr.log.Error().Err(err).Str("cluster", cluster.name).Msg("Token validation request failed") - return false, fmt.Errorf("failed to make validation request: %w", err) - } - defer resp.Body.Close() - - cr.log.Debug().Str("cluster", cluster.name).Int("status", resp.StatusCode).Msg("Token validation response received") - - // Check response status - switch resp.StatusCode { - case http.StatusUnauthorized: - cr.log.Debug().Str("cluster", cluster.name).Msg("Token validation failed - unauthorized") - return false, nil - case http.StatusOK, http.StatusForbidden: - // 200 OK means the token is valid and has access - // 403 Forbidden means the token is valid but doesn't have permission (still authenticated) - cr.log.Debug().Str("cluster", cluster.name).Int("status", resp.StatusCode).Msg("Token validation successful") - return true, nil - default: - // Other status codes indicate an issue with the request or cluster - cr.log.Debug().Str("cluster", cluster.name).Int("status", resp.StatusCode).Msg("Token validation failed with unexpected status") - return false, fmt.Errorf("unexpected status code %d from namespaces endpoint", resp.StatusCode) - } -} - // extractClusterName extracts the cluster name from the request path using pattern matching // Expected formats: // - Regular workspace: /{clusterName}/graphql diff --git a/gateway/manager/targetcluster/registry_test.go b/gateway/manager/targetcluster/registry_test.go index 4e1d381..f508671 100644 --- a/gateway/manager/targetcluster/registry_test.go +++ b/gateway/manager/targetcluster/registry_test.go @@ -18,7 +18,7 @@ func TestExtractClusterNameWithKCPWorkspace(t *testing.T) { appCfg.Url.DefaultKcpWorkspace = "root" appCfg.Url.GraphqlSuffix = "graphql" - registry := NewClusterRegistry(log, appCfg, nil) + registry := NewClusterRegistry(log, appCfg) tests := []struct { name string diff --git a/go.mod b/go.mod index 95d69ab..c1a00b5 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,10 @@ module github.com/platform-mesh/kubernetes-graphql-gateway go 1.24.3 replace ( - github.com/google/cel-go => github.com/google/cel-go v0.26.1 + // github.com/google/cel-go => github.com/google/cel-go v0.26.1 // this PR introduces newer version of graphiQL that supports headers // https://github.com/graphql-go/handler/pull/93 github.com/graphql-go/handler => github.com/vertex451/handler v0.0.0-20250124125145-ed328e3cf42a - k8s.io/api => k8s.io/api v0.33.3 - k8s.io/apimachinery => k8s.io/apimachinery v0.33.3 - k8s.io/client-go => k8s.io/client-go v0.32.4 sigs.k8s.io/controller-runtime => github.com/kcp-dev/controller-runtime v0.19.0-kcp.1.0.20250129100209-5eaf4c7b6056 ) @@ -22,11 +19,11 @@ require ( github.com/graphql-go/graphql v0.8.1 github.com/graphql-go/handler v0.2.4 github.com/hashicorp/go-multierror v1.1.1 - github.com/kcp-dev/kcp/sdk v0.28.3 + github.com/kcp-dev/kcp/sdk v0.28.1-0.20251024082134-d9a41beae3f3 github.com/kcp-dev/logicalcluster/v3 v3.0.5 github.com/pkg/errors v0.9.1 github.com/platform-mesh/account-operator v0.3.1 - github.com/platform-mesh/golang-commons v0.1.32 + github.com/platform-mesh/golang-commons v0.7.4 github.com/prometheus/client_golang v1.23.2 github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.10.1 @@ -37,29 +34,29 @@ require ( golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 golang.org/x/text v0.30.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.33.3 - k8s.io/apiextensions-apiserver v0.33.3 - k8s.io/apimachinery v0.33.3 - k8s.io/client-go v0.33.3 - k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b sigs.k8s.io/controller-runtime v0.22.3 ) require ( cel.dev/expr v0.24.0 // indirect - github.com/99designs/gqlgen v0.17.78 // indirect + github.com/99designs/gqlgen v0.17.81 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/getsentry/sentry-go v0.35.2 // indirect - github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/getsentry/sentry-go v0.36.1 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect @@ -76,8 +73,7 @@ require ( github.com/go-openapi/swag/yamlutils v0.25.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/cel-go v0.26.0 // indirect + github.com/google/cel-go v0.26.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect @@ -86,12 +82,12 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250512171935-ebb573a40077 // indirect + github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/gomega v1.36.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -122,27 +118,26 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/oauth2 v0.31.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.34.0 // indirect + golang.org/x/term v0.35.0 // indirect golang.org/x/time v0.11.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect - google.golang.org/grpc v1.75.1 // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/apiserver v0.33.3 // indirect - k8s.io/component-base v0.33.3 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/component-base v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 68ed714..79fb057 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -github.com/99designs/gqlgen v0.17.78 h1:bhIi7ynrc3js2O8wu1sMQj1YHPENDt3jQGyifoBvoVI= -github.com/99designs/gqlgen v0.17.78/go.mod h1:yI/o31IauG2kX0IsskM4R894OCCG1jXJORhtLQqB7Oc= +github.com/99designs/gqlgen v0.17.81 h1:kCkN/xVyRb5rEQpuwOHRTYq83i0IuTQg9vdIiwEerTs= +github.com/99designs/gqlgen v0.17.81/go.mod h1:vgNcZlLwemsUhYim4dC1pvFP5FX0pr2Y+uYUoHFb1ig= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= @@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.8.0+incompatible h1:1Av9pn2FyxPdvrWNQszj1g6D6YthSmvCfcN6SYclTJg= github.com/evanphx/json-patch v5.8.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -32,14 +32,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/getsentry/sentry-go v0.35.2 h1:jKuujpRwa8FFRYMIwwZpu83Xh0voll9bmvyc6310WBM= -github.com/getsentry/sentry-go v0.35.2/go.mod h1:mdL49ixwT2yi57k5eh7mpnDyPybixPzlzEJFu0Z76QA= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/getsentry/sentry-go v0.36.1 h1:kMJt0WWsxWATUxkvFgVBZdIeHSk/Oiv5P0jZ9e5m/Lw= +github.com/getsentry/sentry-go v0.36.1/go.mod h1:p5Im24mJBeruET8Q4bbcMfCQ+F+Iadc4L48tB1apo2c= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= -github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -90,7 +90,6 @@ github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -117,12 +116,12 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250512171935-ebb573a40077 h1:lDi9nZ75ypmRJwDFXUN70Cdu8+HxAjPU1kcnn+l4MvI= -github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250512171935-ebb573a40077/go.mod h1:jnMZxVnCuKlkIXc4J1Qtmy1Lyo171CDF/RQhNAo0tvA= +github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51 h1:l38RDS+VUMx9etvyaCgJIZa4nM7FaNevNubWN0kDZY4= +github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51/go.mod h1:rF1jfvUfPjFXs+HV/LN1BtPzAz1bfjJOwVa+hAVfroQ= github.com/kcp-dev/controller-runtime v0.19.0-kcp.1.0.20250129100209-5eaf4c7b6056 h1:NaEaA34bHNawPL3npJN8J7jyQhA3eG+UQ0xZvTnOfYo= github.com/kcp-dev/controller-runtime v0.19.0-kcp.1.0.20250129100209-5eaf4c7b6056/go.mod h1:jwK5sBnpu/xJJ+xdpSzzI0aM52E/EvF0uLF9bR61h/Y= -github.com/kcp-dev/kcp/sdk v0.28.3 h1:TS2nJOVBjenBd3fz1+y3aNrqZWqmakalNAIcQM9SukQ= -github.com/kcp-dev/kcp/sdk v0.28.3/go.mod h1:8oZpWxkoMu2TDpx5DgdIGDigByKHKkeqVMA4GiWneoI= +github.com/kcp-dev/kcp/sdk v0.28.1-0.20251024082134-d9a41beae3f3 h1:1aBgU5iK3X25WTBQkV/Vk3DxyIby26dxXGHsgxf7l8k= +github.com/kcp-dev/kcp/sdk v0.28.1-0.20251024082134-d9a41beae3f3/go.mod h1:aC2BPGPvy8QtkI2gQNH9NfW6xpfGIKZkR93gy9O02BE= github.com/kcp-dev/logicalcluster/v3 v3.0.5 h1:JbYakokb+5Uinz09oTXomSUJVQsqfxEvU4RyHUYxHOU= github.com/kcp-dev/logicalcluster/v3 v3.0.5/go.mod h1:EWBUBxdr49fUB1cLMO4nOdBWmYifLbP1LfoL20KkXYY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -147,8 +146,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= @@ -163,8 +163,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/platform-mesh/account-operator v0.3.1 h1:i+QBX3vauHEU+DBg0rSbJCQSKWYa2eSz9qWt1Fj9UAY= github.com/platform-mesh/account-operator v0.3.1/go.mod h1:ytszzXct5SiUdEdcbE5wvtZca0ZiJraflFmw7KGhQY8= -github.com/platform-mesh/golang-commons v0.1.32 h1:bNjuLApJzJWcxtpGkd5+uF8uSA5q+u1XNs6gAYCmkFM= -github.com/platform-mesh/golang-commons v0.1.32/go.mod h1:udKDsBJrdnbzkL5qN2zt63dDVqKQg8tEe63t1G0qT+w= +github.com/platform-mesh/golang-commons v0.7.4 h1:ZIY9ExAZ+BbH1xcn96zw2wR8rlsDST7Soow8yaHG2Mc= +github.com/platform-mesh/golang-commons v0.7.4/go.mod h1:GJe0jJcS9hfT7ajo7sbOe5p2Uw0GuVLeQhZEffKM9os= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -259,8 +259,6 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -269,10 +267,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= -golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -286,8 +284,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= @@ -312,10 +310,10 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1: google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -326,33 +324,31 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= -k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= -k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= -k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= -k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= -k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= -k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= -k8s.io/client-go v0.32.4 h1:zaGJS7xoYOYumoWIFXlcVrsiYioRPrXGO7dBfVC5R6M= -k8s.io/client-go v0.32.4/go.mod h1:k0jftcyYnEtwlFW92xC7MTtFv5BNcZBr+zn9jPlT9Ic= -k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= -k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 h1:gAXU86Fmbr/ktY17lkHwSjw5aoThQvhnstGGIYKlKYc= -k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/listener/reconciler/clusteraccess/subroutines.go b/listener/reconciler/clusteraccess/subroutines.go index 3310709..7d60968 100644 --- a/listener/reconciler/clusteraccess/subroutines.go +++ b/listener/reconciler/clusteraccess/subroutines.go @@ -104,6 +104,6 @@ func (s *generateSchemaSubroutine) GetName() string { return "generate-schema" } -func (s *generateSchemaSubroutine) Finalizers() []string { +func (s *generateSchemaSubroutine) Finalizers(runtimeobject.RuntimeObject) []string { return nil } diff --git a/tests/gateway_test/suite_test.go b/tests/gateway_test/suite_test.go index fd1cdf1..2b86578 100644 --- a/tests/gateway_test/suite_test.go +++ b/tests/gateway_test/suite_test.go @@ -123,7 +123,7 @@ func (suite *CommonTestSuite) SetupTest() { suite.appCfg.LocalDevelopment = suite.LocalDevelopment suite.appCfg.Gateway.Cors.Enabled = true - suite.appCfg.IntrospectionAuthentication = suite.AuthenticateSchemaRequests + suite.appCfg.Gateway.IntrospectionAuthentication = suite.AuthenticateSchemaRequests // Set URL configuration for the gateway tests suite.appCfg.Url.VirtualWorkspacePrefix = "virtual-workspace"