diff --git a/pkg/controller/kubelet-config/helpers.go b/pkg/controller/kubelet-config/helpers.go index b190b48a0d..1ae7b13018 100644 --- a/pkg/controller/kubelet-config/helpers.go +++ b/pkg/controller/kubelet-config/helpers.go @@ -377,9 +377,6 @@ func validateUserKubeletConfig(cfg *mcfgv1.KubeletConfig) error { if len(kcDecoded.FeatureGates) > 0 { return fmt.Errorf("KubeletConfiguration: featureGates is not allowed to be set, but contains: %v", kcDecoded.FeatureGates) } - if kcDecoded.StaticPodPath != "" { - return fmt.Errorf("KubeletConfiguration: staticPodPath is not allowed to be set, but contains: %s", kcDecoded.StaticPodPath) - } if kcDecoded.FailSwapOn != nil { return fmt.Errorf("KubeletConfiguration: failSwapOn is not allowed to be set, but contains: %v", *kcDecoded.FailSwapOn) } @@ -393,6 +390,26 @@ func validateUserKubeletConfig(cfg *mcfgv1.KubeletConfig) error { return nil } +// validateStaticPodPathForPool checks that staticPodPath is not set to a non-empty value. +// Control plane pools (master, arbiter) cannot set staticPodPath at all. +// Worker and custom pools may only set staticPodPath to "" to disable static pods. +func validateStaticPodPathForPool(cfg *mcfgv1.KubeletConfig, poolName string) error { + if cfg.Spec.KubeletConfig == nil || cfg.Spec.KubeletConfig.Raw == nil { + return nil + } + kcDecoded, err := DecodeKubeletConfig(cfg.Spec.KubeletConfig.Raw) + if err != nil { + return fmt.Errorf("KubeletConfig could not be unmarshalled, err: %w", err) + } + if kcDecoded.StaticPodPath == "" { + return nil + } + if poolName == ctrlcommon.MachineConfigPoolMaster || poolName == ctrlcommon.MachineConfigPoolArbiter { + return fmt.Errorf("KubeletConfiguration: staticPodPath is not allowed to be set for pool %q, but contains: %s", poolName, kcDecoded.StaticPodPath) + } + return fmt.Errorf("KubeletConfiguration: staticPodPath must be empty for non-control-plane pool %q, but contains: %s", poolName, kcDecoded.StaticPodPath) +} + func wrapErrorWithCondition(err error, args ...interface{}) mcfgv1.KubeletConfigCondition { var condition *mcfgv1.KubeletConfigCondition if err != nil { diff --git a/pkg/controller/kubelet-config/kubelet_config_bootstrap.go b/pkg/controller/kubelet-config/kubelet_config_bootstrap.go index 2dced82bab..fc33001e5c 100644 --- a/pkg/controller/kubelet-config/kubelet_config_bootstrap.go +++ b/pkg/controller/kubelet-config/kubelet_config_bootstrap.go @@ -40,6 +40,11 @@ func RunKubeletBootstrap(templateDir string, kubeletConfigs []*mcfgv1.KubeletCon } role := pool.Name + // Validate staticPodPath is not set for control plane pools + if err := validateStaticPodPathForPool(kubeletConfig, role); err != nil { + return nil, err + } + originalKubeConfig, err := generateOriginalKubeletConfigWithFeatureGates(controllerConfig, templateDir, role, fgHandler, apiServer) if err != nil { return nil, err diff --git a/pkg/controller/kubelet-config/kubelet_config_controller.go b/pkg/controller/kubelet-config/kubelet_config_controller.go index 2316c0827d..bb2b29e682 100644 --- a/pkg/controller/kubelet-config/kubelet_config_controller.go +++ b/pkg/controller/kubelet-config/kubelet_config_controller.go @@ -597,6 +597,13 @@ func (ctrl *Controller) syncKubeletConfig(key string) error { return ctrl.syncStatusOnly(cfg, err, "could not get the TLSSecurityProfile from %v: %v", ctrlcommon.APIServerInstanceName, err) } + // Validate staticPodPath for all pools before applying any changes + for _, pool := range mcpPools { + if err := validateStaticPodPathForPool(cfg, pool.Name); err != nil { + return ctrl.syncStatusOnly(cfg, err) + } + } + for _, pool := range mcpPools { role := pool.Name // Get MachineConfig diff --git a/pkg/controller/kubelet-config/kubelet_config_controller_test.go b/pkg/controller/kubelet-config/kubelet_config_controller_test.go index a99cf0ccda..c60a216a9c 100644 --- a/pkg/controller/kubelet-config/kubelet_config_controller_test.go +++ b/pkg/controller/kubelet-config/kubelet_config_controller_test.go @@ -913,12 +913,6 @@ func TestKubeletConfigDenylistedOptions(t *testing.T) { ClusterDomain: "some_value", }, }, - { - name: "test banned staticpodpath", - config: &kubeletconfigv1beta1.KubeletConfiguration{ - StaticPodPath: "some_value", - }, - }, { name: "user cannot supply features gates", config: &kubeletconfigv1beta1.KubeletConfiguration{ @@ -974,6 +968,56 @@ func TestKubeletConfigDenylistedOptions(t *testing.T) { } } +func TestStaticPodPathPoolValidation(t *testing.T) { + kcWithNonEmptyStaticPodPath := newKubeletConfig( + "test-staticpodpath-nonempty", + &kubeletconfigv1beta1.KubeletConfiguration{ + StaticPodPath: "/some/path", + }, + metav1.AddLabelToSelector(&metav1.LabelSelector{}, "", ""), + ) + + // non-empty staticPodPath should be rejected for all pools + err := validateStaticPodPathForPool(kcWithNonEmptyStaticPodPath, ctrlcommon.MachineConfigPoolMaster) + if err == nil { + t.Error("expected error for non-empty staticPodPath on master pool, got nil") + } + + err = validateStaticPodPathForPool(kcWithNonEmptyStaticPodPath, ctrlcommon.MachineConfigPoolArbiter) + if err == nil { + t.Error("expected error for non-empty staticPodPath on arbiter pool, got nil") + } + + err = validateStaticPodPathForPool(kcWithNonEmptyStaticPodPath, ctrlcommon.MachineConfigPoolWorker) + if err == nil { + t.Error("expected error for non-empty staticPodPath on worker pool, got nil") + } + + err = validateStaticPodPathForPool(kcWithNonEmptyStaticPodPath, "custom-pool") + if err == nil { + t.Error("expected error for non-empty staticPodPath on custom pool, got nil") + } + + // empty staticPodPath should be allowed for worker and custom pools + kcWithEmptyStaticPodPath := newKubeletConfig( + "test-staticpodpath-empty", + &kubeletconfigv1beta1.KubeletConfiguration{ + StaticPodPath: "", + }, + metav1.AddLabelToSelector(&metav1.LabelSelector{}, "", ""), + ) + + err = validateStaticPodPathForPool(kcWithEmptyStaticPodPath, ctrlcommon.MachineConfigPoolWorker) + if err != nil { + t.Errorf("expected no error for empty staticPodPath on worker pool, got: %v", err) + } + + err = validateStaticPodPathForPool(kcWithEmptyStaticPodPath, "custom-pool") + if err != nil { + t.Errorf("expected no error for empty staticPodPath on custom pool, got: %v", err) + } +} + func TestKubeletConfigLogLevel(t *testing.T) { // Test cases for valid LogLevel values validLogLevels := []struct {