diff --git a/pkg/features/kcp_features.go b/pkg/features/kcp_features.go index 375fae53291..a10b1ef680f 100644 --- a/pkg/features/kcp_features.go +++ b/pkg/features/kcp_features.go @@ -62,6 +62,18 @@ const ( WorkspaceAuthentication featuregate.Feature = "WorkspaceAuthentication" ) +// Re-export upstream feature gates used by kcp server logic so callers import +// only this package instead of mixing kcp and upstream feature imports. +// Mirrored from k8s.io/apiserver/pkg/features. +const ( + // StorageVersionAPI enables the storage version API (alpha since k8s 1.20, off by default). + // When enabled in kcp, WithStorageVersionPrecondition is applied in the handler chain to + // block write requests to resources whose storage versions have not yet converged across + // all kcp shards during a rolling upgrade. + // See: https://github.com/kcp-dev/kubernetes/pull/185 + StorageVersionAPI = genericfeatures.StorageVersionAPI +) + // DefaultFeatureGate exposes the upstream feature gate, but with our gate setting applied. var DefaultFeatureGate = utilfeature.DefaultFeatureGate @@ -141,6 +153,11 @@ var defaultVersionedGenericControlPlaneFeatureGates = map[featuregate.Feature]fe WorkspaceAuthentication: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, }, + // StorageVersionAPI mirrors the upstream k8s gate; kcp keeps it Alpha/off-by-default. + // Must be listed here so kcp's feature gate machinery tracks it and exposes it via --feature-gates. + genericfeatures.StorageVersionAPI: { + {Version: version.MustParse("1.20"), Default: false, PreRelease: featuregate.Alpha}, + }, // inherited features from generic apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: genericfeatures.APIResponseCompression: { diff --git a/pkg/server/config.go b/pkg/server/config.go index c2ce0bfca9b..da5177201e9 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -453,6 +453,14 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co // preHandlerChainMux is called before the actual handler chain. Note that BuildHandlerChainFunc below // is called multiple times, but only one of the handler chain will actually be used. Hence, we wrap it // to give handlers below one mux.Handle func to call. + // + // NOTE: kcp fully replaces BuildHandlerChainFunc here, which means any handler chain customization + // done earlier in the setup pipeline (e.g. genericConfig.BuildHandlerChainFunc = + // genericapiserver.BuildHandlerChainWithStorageVersionPrecondition in CreateAggregatorConfig when + // StorageVersionAPI + APIServerIdentity feature gates are enabled) is overwritten and never applied. + // WithStorageVersionPrecondition is therefore applied explicitly below, gated behind the + // StorageVersionAPI feature gate (alpha, off by default) to match upstream k8s behaviour. + // See: https://github.com/kcp-dev/kubernetes/pull/185 c.preHandlerChainMux = &handlerChainMuxes{} c.GenericConfig.BuildHandlerChainFunc = func(apiHandler http.Handler, genericConfig *genericapiserver.Config) (secure http.Handler) { apiHandler = openapiv3.WithOpenAPIv3(apiHandler, c.openAPIv3ServiceCache) // will be initialized further down after apiextensions-apiserver @@ -461,6 +469,16 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co apiHandler = authorization.WithSubjectAccessReviewAuditAnnotations(apiHandler) apiHandler = authorization.WithDeepSubjectAccessReview(apiHandler) + // WithStorageVersionPrecondition blocks write requests to resources whose storage versions + // have not yet converged across all kcp shards during a rolling upgrade. It must wrap the + // API handler before the authz chain runs, and relies on WithRequestInfo + // (in DefaultBuildHandlerChainFromStartToBeforeImpersonation below) being present in the + // outer chain at request time. + // Gated behind StorageVersionAPI (alpha, off by default) to match upstream k8s behaviour. + if kcpfeatures.DefaultFeatureGate.Enabled(kcpfeatures.StorageVersionAPI) { + apiHandler = filters.WithStorageVersionPrecondition(apiHandler, genericConfig.StorageVersionManager, genericConfig.Serializer) + } + // The following ensures that only the default main api handler chain executes authorizers which log audit messages. // All other invocations of the same authorizer chain still work but do not produce audit log entries. // This compromises audit log size and information overflow vs. having audit reasons for the main api handler only.