diff --git a/hack/verify-test-featuregates.sh b/hack/verify-test-featuregates.sh index 80f7af87ac2..947fa250f6c 100755 --- a/hack/verify-test-featuregates.sh +++ b/hack/verify-test-featuregates.sh @@ -41,19 +41,4 @@ if [[ -n "${direct_sets}" ]]; then rc=1 fi -export LC_ALL=C -# ensure all generic features are added in alphabetic order -lines=$(git grep 'genericfeatures[.].*:' -- pkg/features/kube_features.go) -sorted_lines=$(echo "$lines" | sort) -if [[ "$lines" != "$sorted_lines" ]]; then - echo "Generic features in pkg/features/kube_features.go not sorted" >&2 - echo >&2 - echo "Expected:" >&2 - echo "$sorted_lines" >&2 - echo >&2 - echo "Got:" >&2 - echo "$lines" >&2 - rc=1 -fi - exit $rc diff --git a/pkg/controlplane/apiserver/options/validation.go b/pkg/controlplane/apiserver/options/validation.go index deb9c6dbe18..0d81c90bf44 100644 --- a/pkg/controlplane/apiserver/options/validation.go +++ b/pkg/controlplane/apiserver/options/validation.go @@ -23,7 +23,6 @@ import ( "strings" apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver" - genericfeatures "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" "k8s.io/kubernetes/pkg/features" @@ -71,20 +70,6 @@ func validateAPIPriorityAndFairness(options *Options) []error { return nil } -func validateNodeSelectorAuthorizationFeature() []error { - if utilfeature.DefaultFeatureGate.Enabled(features.AuthorizeNodeWithSelectors) && !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) { - return []error{fmt.Errorf("AuthorizeNodeWithSelectors feature requires AuthorizeWithSelectors feature to be enabled")} - } - return nil -} - -func validatePodCertificateRequestFeature() []error { - if utilfeature.DefaultFeatureGate.Enabled(features.PodCertificateRequest) && !utilfeature.DefaultFeatureGate.Enabled(features.AuthorizeNodeWithSelectors) { - return []error{fmt.Errorf("PodCertificateRequest feature requires AuthorizeNodeWithSelectors feature to be enabled")} - } - return nil -} - func validateUnknownVersionInteroperabilityProxyFlags(options *Options) []error { err := []error{} if !utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) { @@ -151,8 +136,6 @@ func (s *Options) Validate() []error { errs = append(errs, validateTokenRequest(s)...) errs = append(errs, s.Metrics.Validate()...) errs = append(errs, validateUnknownVersionInteroperabilityProxyFlags(s)...) - errs = append(errs, validateNodeSelectorAuthorizationFeature()...) - errs = append(errs, validatePodCertificateRequestFeature()...) errs = append(errs, validateServiceAccountTokenSigningConfig(s)...) errs = append(errs, validateCoordinatedLeadershipFlags(s)...) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 9f17414f8c0..9f093ce8ffe 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -1999,10 +1999,387 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate // // Entries are alphabetized. var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]featuregate.Feature{ - DisableAllocatorDualWrite: {MultiCIDRServiceAllocator}, + AllowDNSOnlyNodeCSR: {}, + + AllowInsecureKubeletCertificateSigningRequests: {}, + + AllowOverwriteTerminationGracePeriodSeconds: {}, + + AllowServiceLBStatusOnNonLB: {}, + + AnyVolumeDataSource: {}, + + AuthorizeNodeWithSelectors: {genericfeatures.AuthorizeWithSelectors}, + + CPUCFSQuotaPeriod: {}, + + CPUManagerPolicyAlphaOptions: {}, + + CPUManagerPolicyBetaOptions: {}, + + CPUManagerPolicyOptions: {}, + + CSIMigrationPortworx: {}, + + CSIVolumeHealth: {}, + + ClearingNominatedNodeNameAfterBinding: {}, + + ClusterTrustBundle: {}, + + ClusterTrustBundleProjection: {ClusterTrustBundle}, + + ContainerCheckpoint: {}, + + ContainerRestartRules: {}, + + ContainerStopSignals: {}, + + CoordinatedLeaderElection: {}, + + CrossNamespaceVolumeDataSource: {}, + + DRAAdminAccess: {DynamicResourceAllocation}, + + DRAConsumableCapacity: {DynamicResourceAllocation}, + + DRADeviceBindingConditions: {DynamicResourceAllocation, DRAResourceClaimDeviceStatus}, + + DRADeviceTaints: {DynamicResourceAllocation}, + + DRAExtendedResource: {DynamicResourceAllocation}, + + DRAPartitionableDevices: {DynamicResourceAllocation}, + + DRAPrioritizedList: {DynamicResourceAllocation}, + + DRAResourceClaimDeviceStatus: {}, // Soft dependency on DynamicResourceAllocation due to on/off-by-default conflict. + + DRASchedulerFilterTimeout: {DynamicResourceAllocation}, + + DeploymentReplicaSetTerminatingReplicas: {}, + + DisableAllocatorDualWrite: {MultiCIDRServiceAllocator}, + + DisableCPUQuotaWithExclusiveCPUs: {}, + + DisableNodeKubeProxyVersion: {}, + + DynamicResourceAllocation: {}, + + EnvFiles: {}, + + EventedPLEG: {}, + + ExecProbeTimeout: {}, + + ExternalServiceAccountTokenSigner: {}, + + GitRepoVolumeDriver: {}, + + GracefulNodeShutdown: {}, + + GracefulNodeShutdownBasedOnPodPriority: {GracefulNodeShutdown}, + + HPAConfigurableTolerance: {}, + + HPAScaleToZero: {}, + + HonorPVReclaimPolicy: {}, + + HostnameOverride: {}, + + ImageMaximumGCAge: {}, + + ImageVolume: {}, + + InPlacePodVerticalScaling: {}, + InPlacePodVerticalScalingAllocatedStatus: {InPlacePodVerticalScaling}, - InPlacePodVerticalScalingExclusiveCPUs: {InPlacePodVerticalScaling}, + + InPlacePodVerticalScalingExclusiveCPUs: {InPlacePodVerticalScaling}, + InPlacePodVerticalScalingExclusiveMemory: {InPlacePodVerticalScaling, MemoryManager}, + + InTreePluginPortworxUnregister: {}, + + JobBackoffLimitPerIndex: {}, + + JobManagedBy: {}, + + JobPodReplacementPolicy: {}, + + JobSuccessPolicy: {}, + + KubeletCgroupDriverFromCRI: {}, + + KubeletCrashLoopBackOffMax: {}, + + KubeletEnsureSecretPulledImages: {}, + + KubeletFineGrainedAuthz: {}, + + KubeletInUserNamespace: {}, + + KubeletPSI: {}, + + KubeletPodResourcesDynamicResources: {}, + + KubeletPodResourcesGet: {}, + + KubeletPodResourcesListUseActivePods: {}, + + KubeletRegistrationGetOnExistsOnly: {}, + + KubeletSeparateDiskGC: {}, + + KubeletServiceAccountTokenForCredentialProviders: {}, + + KubeletTracing: {}, + + LoadBalancerIPMode: {}, + + LocalStorageCapacityIsolationFSQuotaMonitoring: {}, + + LogarithmicScaleDown: {}, + + MatchLabelKeysInPodAffinity: {}, + + MatchLabelKeysInPodTopologySpread: {}, + + MatchLabelKeysInPodTopologySpreadSelectorMerge: {MatchLabelKeysInPodTopologySpread}, + + MaxUnavailableStatefulSet: {}, + + MemoryManager: {}, + + MemoryQoS: {}, + + MultiCIDRServiceAllocator: {}, + + MutableCSINodeAllocatableCount: {}, + + NFTablesProxyMode: {}, + + NodeInclusionPolicyInPodTopologySpread: {}, + + NodeLogQuery: {}, + + NodeSwap: {}, + + NominatedNodeNameForExpectation: {}, + + OrderedNamespaceDeletion: {}, + + PodAndContainerStatsFromCRI: {}, + + PodCertificateRequest: {AuthorizeNodeWithSelectors}, + + PodDeletionCost: {}, + + PodLevelResources: {}, + + PodLifecycleSleepAction: {}, + + PodLifecycleSleepActionAllowZero: {PodLifecycleSleepAction}, + + PodLogsQuerySplitStreams: {}, + + PodObservedGenerationTracking: {}, + + PodReadyToStartContainersCondition: {}, + + PodSchedulingReadiness: {}, + + PodTopologyLabelsAdmission: {}, + + PortForwardWebsockets: {}, + + PreferSameTrafficDistribution: {}, + + PreventStaticPodAPIReferences: {}, + + ProcMountType: {UserNamespacesSupport}, + + QOSReserved: {}, + + RecoverVolumeExpansionFailure: {}, + + RecursiveReadOnlyMounts: {}, + + ReduceDefaultCrashLoopBackOffDecay: {}, + + RelaxedDNSSearchValidation: {}, + + RelaxedEnvironmentVariableValidation: {}, + + RelaxedServiceNameValidation: {}, + + ReloadKubeletServerCertificateFile: {}, + + ResourceHealthStatus: {DynamicResourceAllocation}, + + RotateKubeletServerCertificate: {}, + + RuntimeClassInImageCriAPI: {}, + + SELinuxChangePolicy: {}, + + SELinuxMount: {}, + + SELinuxMountReadWriteOncePod: {}, + + SchedulerAsyncAPICalls: {}, + + SchedulerAsyncPreemption: {}, + + SchedulerPopFromBackoffQ: {}, + + SchedulerQueueingHints: {}, + + SeparateTaintEvictionController: {}, + + ServiceAccountNodeAudienceRestriction: {}, + + ServiceAccountTokenJTI: {}, + + ServiceAccountTokenNodeBinding: {ServiceAccountTokenNodeBindingValidation}, + + ServiceAccountTokenNodeBindingValidation: {}, + + ServiceAccountTokenPodNodeInfo: {}, + + ServiceTrafficDistribution: {}, + + SidecarContainers: {}, + + StorageCapacityScoring: {}, + + StorageNamespaceIndex: {}, + + StorageVersionMigrator: {}, + + StreamingCollectionEncodingToJSON: {}, + + StreamingCollectionEncodingToProtobuf: {}, + + StrictIPCIDRValidation: {}, + + SupplementalGroupsPolicy: {}, + + SystemdWatchdog: {}, + + TopologyAwareHints: {}, + + TopologyManagerPolicyAlphaOptions: {}, + + TopologyManagerPolicyBetaOptions: {}, + + TopologyManagerPolicyOptions: {}, + + TranslateStreamCloseWebsocketRequests: {}, + + UnknownVersionInteroperabilityProxy: {}, + + UserNamespacesPodSecurityStandards: {}, + + UserNamespacesSupport: {}, + + VolumeAttributesClass: {}, + + WinDSR: {}, + + WinOverlay: {}, + + WindowsCPUAndMemoryAffinity: {MemoryManager}, + + WindowsGracefulNodeShutdown: {GracefulNodeShutdown}, + + WindowsHostNetwork: {}, + + apiextensionsfeatures.CRDValidationRatcheting: {}, + + apiextensionsfeatures.CustomResourceFieldSelectors: {}, + + genericfeatures.APIResponseCompression: {}, + + genericfeatures.APIServerIdentity: {}, + + genericfeatures.APIServerTracing: {}, + + genericfeatures.APIServingWithRoutine: {}, + + genericfeatures.AggregatedDiscoveryRemoveBetaType: {}, + + genericfeatures.AllowParsingUserUIDFromCertAuth: {}, + + genericfeatures.AllowUnsafeMalformedObjectDeletion: {}, + + genericfeatures.AnonymousAuthConfigurableEndpoints: {}, + + genericfeatures.AuthorizeWithSelectors: {}, + + genericfeatures.BtreeWatchCache: {}, + + genericfeatures.CBORServingAndStorage: {}, + + genericfeatures.ConcurrentWatchObjectDecode: {}, + + genericfeatures.ConsistentListFromCache: {}, + + genericfeatures.DeclarativeValidation: {}, + + genericfeatures.DeclarativeValidationTakeover: {genericfeatures.DeclarativeValidation}, + + genericfeatures.DetectCacheInconsistency: {}, + + genericfeatures.KMSv1: {}, + + genericfeatures.ListFromCacheSnapshot: {}, + + genericfeatures.MutatingAdmissionPolicy: {}, + + genericfeatures.OpenAPIEnums: {}, + + genericfeatures.RemoteRequestHeaderUID: {}, + + genericfeatures.ResilientWatchCacheInitialization: {}, + + genericfeatures.RetryGenerateName: {}, + + genericfeatures.SeparateCacheWatchRPC: {}, + + genericfeatures.SizeBasedListCostEstimate: {}, + + genericfeatures.StorageVersionAPI: {genericfeatures.APIServerIdentity}, + + genericfeatures.StorageVersionHash: {}, + + genericfeatures.StrictCostEnforcementForVAP: {}, + + genericfeatures.StrictCostEnforcementForWebhooks: {}, + + genericfeatures.StructuredAuthenticationConfiguration: {}, + + genericfeatures.StructuredAuthenticationConfigurationEgressSelector: {genericfeatures.StructuredAuthenticationConfiguration}, + + genericfeatures.StructuredAuthorizationConfiguration: {}, + + genericfeatures.TokenRequestServiceAccountUIDValidation: {}, + + genericfeatures.UnauthenticatedHTTP2DOSMitigation: {}, + + genericfeatures.WatchCacheInitializationPostStartHook: {}, + + genericfeatures.WatchFromStorageWithoutResourceVersion: {}, + + genericfeatures.WatchList: {}, + + kcmfeatures.CloudControllerManagerWebhook: {}, + + zpagesfeatures.ComponentFlagz: {}, + + zpagesfeatures.ComponentStatusz: {}, } func init() { diff --git a/pkg/features/kube_features_test.go b/pkg/features/kube_features_test.go index 29a147b288a..cfaace1e831 100644 --- a/pkg/features/kube_features_test.go +++ b/pkg/features/kube_features_test.go @@ -17,6 +17,8 @@ limitations under the License. package features import ( + "maps" + "slices" "testing" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -91,3 +93,12 @@ func TestEnsureAlphaGatesAreNotSwitchedOnByDefault(t *testing.T) { } } } + +func TestAllDependenciesRegistered(t *testing.T) { + registeredDependencies := utilfeature.DefaultFeatureGate.Dependencies() + for _, f := range slices.Sorted(maps.Keys(defaultVersionedKubernetesFeatureGates)) { + if _, depsRegistered := registeredDependencies[f]; !depsRegistered { + t.Errorf("Feature %s did not register dependencies. All features must record explicit feature dependencies, even if there are none.", f) + } + } +} diff --git a/pkg/kubeapiserver/options/authentication.go b/pkg/kubeapiserver/options/authentication.go index f11bff75566..c3fed18b31e 100644 --- a/pkg/kubeapiserver/options/authentication.go +++ b/pkg/kubeapiserver/options/authentication.go @@ -295,11 +295,6 @@ func (o *BuiltInAuthenticationOptions) Validate() []error { } } - // verify that if ServiceAccountTokenNodeBinding is enabled, ServiceAccountTokenNodeBindingValidation is also enabled. - if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBinding) && !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBindingValidation) { - allErrors = append(allErrors, fmt.Errorf("the %q feature gate can only be enabled if the %q feature gate is also enabled", features.ServiceAccountTokenNodeBinding, features.ServiceAccountTokenNodeBindingValidation)) - } - if o.WebHook != nil { retryBackoff := o.WebHook.RetryBackoff if retryBackoff != nil && retryBackoff.Steps <= 0 {