diff --git a/feature/feature.go b/feature/feature.go index 91ff0c29..08274fd7 100644 --- a/feature/feature.go +++ b/feature/feature.go @@ -93,6 +93,26 @@ const ( // When enabled, the work controller will automatically clean up completed manifest works based on the configured // time-to-live duration to prevent accumulation of old completed resources. CleanUpCompletedManifestWork featuregate.Feature = "CleanUpCompletedManifestWork" + + // ClusterProxy integrates cluster-proxy functionality directly into the klusterlet-agent, enabling + // HTTP-based proxying to managed cluster API servers through gRPC tunnels. + // + // When enabled on the hub (via ClusterManager), it starts a gRPC server that provides an externally accessible + // HTTP endpoint to proxy requests to managed cluster API servers. The API path format is: + // https://:/ + // + // When enabled on the spoke (via Klusterlet), the agent establishes a gRPC connection to the hub and proxies + // requests to its local API server. The agent sends a CSR with signer name "open-cluster-management.io/klusterlet-proxy" + // to obtain the gRPC configuration, which is stored in the hub-kubeconfig-secret as "proxy-grpc.yaml". + // + // This feature requires gRPC configuration in ClusterManager.spec.grpcConfiguration with the ClusterProxy + // feature gate enabled. Users can authenticate using either userToken or impersonation methods. + // + // Use cases include: fetching pod logs, accessing VM consoles (kubevirt), multicluster job submission (MultiKueue), + // and multicluster apiserver access (Kubernetes MCP). + // + // When disabled, the legacy cluster-proxy addon should be used instead for proxy functionality. + ClusterProxy featuregate.Feature = "ClusterProxy" ) // DefaultSpokeRegistrationFeatureGates consists of all known ocm-registration @@ -137,3 +157,7 @@ var DefaultSpokeWorkFeatureGates = map[featuregate.Feature]featuregate.FeatureSp ExecutorValidatingCaches: {Default: false, PreRelease: featuregate.Alpha}, RawFeedbackJsonString: {Default: false, PreRelease: featuregate.Alpha}, } + +var DefaultServerConfigFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + ClusterProxy: {Default: false, PreRelease: featuregate.Alpha}, +} diff --git a/operator/v1/0000_00_operator.open-cluster-management.io_klusterlets.crd.yaml b/operator/v1/0000_00_operator.open-cluster-management.io_klusterlets.crd.yaml index 7137dc60..e24257a2 100644 --- a/operator/v1/0000_00_operator.open-cluster-management.io_klusterlets.crd.yaml +++ b/operator/v1/0000_00_operator.open-cluster-management.io_klusterlets.crd.yaml @@ -181,6 +181,112 @@ spec: deployed klusterlet agent. It will be ignored when the PriorityClass/v1 API is not available on the managed cluster. type: string + proxyConfig: + description: |- + ProxyConfig holds the configuration for enabling klusterlet-proxy functionality, + which allows the hub cluster to access the managed cluster's API server through + a gRPC-based proxy tunnel established by the klusterlet agent. + + When configured, the klusterlet agent establishes a gRPC connection to the hub's + proxy server and proxies incoming HTTP requests to the local managed cluster API server. + This enables hub-to-spoke API access even when the managed cluster is not directly + accessible from the hub (e.g., behind a firewall or NAT). + + This feature requires the ClusterProxy feature gate to be enabled and corresponding + GRPCConfiguration to be set in the ClusterManager on the hub side. + properties: + authentications: + default: + - userToken + description: |- + Authentications defines how the agent authenticates with the cluster. + If not specified, defaults to ["userToken"]. + items: + enum: + - userToken + - impersonation + type: string + type: array + grpcEndpoint: + description: GRPCEndpoint represents the gRPC endpoint configuration + for the proxy connection. + properties: + grpc: + description: grpc represents the configuration for grpc endpoint. + properties: + hostname: + description: hostname points to a fixed hostname for serving + agents' handshakes. + properties: + caBundle: + description: caBundle of the endpoint. + format: byte + type: string + host: + description: host is the host name of the endpoint. + type: string + required: + - host + type: object + type: + default: hostname + description: |- + type specifies how the endpoint is exposed. + You may need to apply an object to expose the endpoint, for example: a route. + enum: + - hostname + type: string + required: + - type + type: object + https: + description: https represents the configuration for https + endpoint. + properties: + hostname: + description: hostname points to a fixed hostname for serving + agents' handshakes. + properties: + caBundle: + description: caBundle of the endpoint. + format: byte + type: string + host: + description: host is the host name of the endpoint. + type: string + required: + - host + type: object + type: + default: hostname + description: |- + type specifies how the endpoint is exposed. + You may need to apply an object to expose the endpoint, for example: a route. + enum: + - hostname + type: string + required: + - type + type: object + protocol: + default: grpc + description: protocol is the protocol used for the endpoint, + could be https or grpc. + enum: + - grpc + - https + type: string + usage: + description: |- + usage defines the usage of the endpoint. It could be "agentToHub" indicating the endpoint is used + for communication between agent and hub, or "consumer" indicating the endpoint is used for external consumer. + type: string + required: + - protocol + type: object + required: + - grpcEndpoint + type: object registrationConfiguration: description: RegistrationConfiguration contains the configuration of registration diff --git a/operator/v1/types_klusterlet.go b/operator/v1/types_klusterlet.go index f5b054a7..d78feecf 100644 --- a/operator/v1/types_klusterlet.go +++ b/operator/v1/types_klusterlet.go @@ -100,6 +100,20 @@ type KlusterletSpec struct { // is not available on the managed cluster. // +optional PriorityClassName string `json:"priorityClassName,omitempty"` + + // ProxyConfig holds the configuration for enabling klusterlet-proxy functionality, + // which allows the hub cluster to access the managed cluster's API server through + // a gRPC-based proxy tunnel established by the klusterlet agent. + // + // When configured, the klusterlet agent establishes a gRPC connection to the hub's + // proxy server and proxies incoming HTTP requests to the local managed cluster API server. + // This enables hub-to-spoke API access even when the managed cluster is not directly + // accessible from the hub (e.g., behind a firewall or NAT). + // + // This feature requires the ClusterProxy feature gate to be enabled and corresponding + // GRPCConfiguration to be set in the ClusterManager on the hub side. + // +optional + ProxyConfig *ProxyConfig `json:"proxyConfig,omitempty"` } // ServerURL represents the apiserver url and ca bundle that is accessible externally @@ -331,6 +345,28 @@ const ( ClusterAnnotationsKeyPrefix = "agent.open-cluster-management.io" ) +// ProxyConfig holds the configuration of klusterlet-proxy. +type ProxyConfig struct { + // GRPCEndpoint represents the gRPC endpoint configuration for the proxy connection. + // +required + // +kubebuilder:validation:Required + GRPCEndpoint *EndpointExposure `json:"grpcEndpoint"` + + // Authentications defines how the agent authenticates with the cluster. + // If not specified, defaults to ["userToken"]. + // +optional + // +kubebuilder:default={userToken} + Authentications []ProxyAuthenticationType `json:"authentications,omitempty"` +} + +// +kubebuilder:validation:Enum=userToken;impersonation +type ProxyAuthenticationType string + +const ( + ProxyAuthenticationUserToken ProxyAuthenticationType = "userToken" + ProxyAuthenticationImpersonation ProxyAuthenticationType = "impersonation" +) + // KlusterletDeployOption describes the deployment options for klusterlet type KlusterletDeployOption struct { // Mode can be Default, Hosted, Singleton or SingletonHosted. It is Default mode if not specified diff --git a/operator/v1/zz_generated.deepcopy.go b/operator/v1/zz_generated.deepcopy.go index 66720221..cae18a2c 100644 --- a/operator/v1/zz_generated.deepcopy.go +++ b/operator/v1/zz_generated.deepcopy.go @@ -641,6 +641,11 @@ func (in *KlusterletSpec) DeepCopyInto(out *KlusterletSpec) { *out = new(ResourceRequirement) (*in).DeepCopyInto(*out) } + if in.ProxyConfig != nil { + in, out := &in.ProxyConfig, &out.ProxyConfig + *out = new(ProxyConfig) + (*in).DeepCopyInto(*out) + } return } @@ -754,6 +759,32 @@ func (in *NodePlacement) DeepCopy() *NodePlacement { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyConfig) DeepCopyInto(out *ProxyConfig) { + *out = *in + if in.GRPCEndpoint != nil { + in, out := &in.GRPCEndpoint, &out.GRPCEndpoint + *out = new(EndpointExposure) + (*in).DeepCopyInto(*out) + } + if in.Authentications != nil { + in, out := &in.Authentications, &out.Authentications + *out = make([]ProxyAuthenticationType, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfig. +func (in *ProxyConfig) DeepCopy() *ProxyConfig { + if in == nil { + return nil + } + out := new(ProxyConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RegistrationConfiguration) DeepCopyInto(out *RegistrationConfiguration) { *out = *in