Skip to content

Commit e8b681a

Browse files
committed
Handle MutatingAdmissionPolicy beta graduation in K8s >= 1.34 and for v1 in K8s >= 1.36
1 parent e5018c7 commit e8b681a

File tree

4 files changed

+297
-5
lines changed

4 files changed

+297
-5
lines changed

charts/internal/shoot-system-components/charts/calico-mutating-admission-policy/templates/mutating-admission-policy.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{- if .Values.enabled }}
22
---
3-
apiVersion: admissionregistration.k8s.io/v1alpha1
3+
apiVersion: admissionregistration.k8s.io/{{ .Values.apiVersion }}
44
kind: MutatingAdmissionPolicyBinding
55
metadata:
66
name: block-calico-network-unavailable-binding
@@ -10,7 +10,7 @@ spec:
1010
# MutatingAdmissionPolicy to block Calico from setting the NetworkUnavailable condition on nodes.
1111
# This policy intercepts node/status updates from the calico-node service account and removes
1212
# any changes to the NetworkUnavailable condition, effectively preserving the existing condition.
13-
apiVersion: admissionregistration.k8s.io/v1alpha1
13+
apiVersion: admissionregistration.k8s.io/{{ .Values.apiVersion }}
1414
kind: MutatingAdmissionPolicy
1515
metadata:
1616
name: block-calico-network-unavailable
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
enabled: false
2+
apiVersion: v1alpha1

pkg/controller/controlplane/valuesprovider.go

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
3030
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
3131
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
32+
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
3233
appsv1 "k8s.io/api/apps/v1"
3334
corev1 "k8s.io/api/core/v1"
3435
policyv1 "k8s.io/api/policy/v1"
@@ -306,8 +307,10 @@ var (
306307
{
307308
Name: "calico-mutating-admission-policy",
308309
Objects: []*chart.Object{
309-
{Type: &admissionregistrationv1alpha1.MutatingAdmissionPolicy{}, Name: "calico-mutating-admission-policy"},
310+
{Type: &admissionregistrationv1alpha1.MutatingAdmissionPolicy{}, Name: "block-calico-network-unavailable"},
310311
{Type: &admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding{}, Name: "block-calico-network-unavailable-binding"},
312+
{Type: &admissionregistrationv1beta1.MutatingAdmissionPolicy{}, Name: "block-calico-network-unavailable"},
313+
{Type: &admissionregistrationv1beta1.MutatingAdmissionPolicyBinding{}, Name: "block-calico-network-unavailable-binding"},
311314
},
312315
},
313316
},
@@ -898,14 +901,19 @@ func getControlPlaneShootChartValues(
898901

899902
isIPv6SingleStack := networkingConfig != nil && v1beta1.IsIPv6SingleStack(networkingConfig.IPFamilies)
900903

904+
calicoMutatingAdmissionPolicyValues := map[string]interface{}{"enabled": mutatingAdmissionPolicyEnabled}
905+
if mutatingAdmissionPolicyEnabled {
906+
calicoMutatingAdmissionPolicyValues["apiVersion"] = mutatingAdmissionPolicyAPIVersion(cluster)
907+
}
908+
901909
return map[string]interface{}{
902910
aws.CloudControllerManagerName: map[string]interface{}{"enabled": true},
903911
aws.AWSCustomRouteControllerName: map[string]interface{}{"enabled": customRouteControllerEnabled},
904912
aws.AWSIPAMControllerImageName: map[string]interface{}{"enabled": ipamControllerEnabled},
905913
aws.AWSLoadBalancerControllerName: albValues,
906914
aws.CSINodeName: csiDriverNodeValues,
907915
aws.CSIEfsNodeName: getControlPlaneShootChartCSIEfsValues(infraConfig, infraStatus, isIPv6SingleStack),
908-
"calico-mutating-admission-policy": map[string]interface{}{"enabled": mutatingAdmissionPolicyEnabled},
916+
"calico-mutating-admission-policy": calicoMutatingAdmissionPolicyValues,
909917
}, nil
910918
}
911919

@@ -952,7 +960,32 @@ func isUsingCalico(cluster *extensionscontroller.Cluster) bool {
952960
*cluster.Shoot.Spec.Networking.Type == "calico"
953961
}
954962

963+
// constraintK8sGreaterEqual136 is a version constraint for Kubernetes versions >= 1.36.
964+
var constraintK8sGreaterEqual136 = versionutils.MustNewConstraint(">= 1.36-0")
965+
955966
func isMutatingAdmissionPolicyEnabled(cluster *extensionscontroller.Cluster) bool {
967+
k8sVersion, err := semver.NewVersion(cluster.Shoot.Spec.Kubernetes.Version)
968+
if err != nil {
969+
return false
970+
}
971+
972+
// For K8s >= 1.34, MutatingAdmissionPolicy is beta (enabled by default) or GA (>= 1.36).
973+
// For beta (>= 1.34 and < 1.36), users can explicitly disable the feature gate.
974+
// For GA (>= 1.36), the feature gate is locked on and cannot be disabled.
975+
if versionutils.ConstraintK8sGreaterEqual134.Check(k8sVersion) {
976+
if constraintK8sGreaterEqual136.Check(k8sVersion) {
977+
return true
978+
}
979+
// Beta: check if user explicitly disabled the feature gate
980+
if cluster.Shoot.Spec.Kubernetes.KubeAPIServer != nil &&
981+
cluster.Shoot.Spec.Kubernetes.KubeAPIServer.FeatureGates != nil {
982+
if enabled, ok := cluster.Shoot.Spec.Kubernetes.KubeAPIServer.FeatureGates["MutatingAdmissionPolicy"]; ok && !enabled {
983+
return false
984+
}
985+
}
986+
return true
987+
}
988+
956989
if cluster.Shoot.Spec.Kubernetes.KubeAPIServer == nil {
957990
return false
958991
}
@@ -969,9 +1002,36 @@ func isMutatingAdmissionPolicyEnabled(cluster *extensionscontroller.Cluster) boo
9691002
return false
9701003
}
9711004

972-
if enabled, ok := cluster.Shoot.Spec.Kubernetes.KubeAPIServer.RuntimeConfig["admissionregistration.k8s.io/v1alpha1"]; !ok || !enabled {
1005+
rc := cluster.Shoot.Spec.Kubernetes.KubeAPIServer.RuntimeConfig
1006+
if !rc["admissionregistration.k8s.io/v1alpha1"] && !rc["admissionregistration.k8s.io/v1beta1"] {
9731007
return false
9741008
}
9751009

9761010
return true
9771011
}
1012+
1013+
func mutatingAdmissionPolicyAPIVersion(cluster *extensionscontroller.Cluster) string {
1014+
k8sVersion, err := semver.NewVersion(cluster.Shoot.Spec.Kubernetes.Version)
1015+
if err != nil {
1016+
return "v1alpha1"
1017+
}
1018+
1019+
// For K8s >= 1.36, MutatingAdmissionPolicy is GA
1020+
if constraintK8sGreaterEqual136.Check(k8sVersion) {
1021+
return "v1"
1022+
}
1023+
1024+
// For K8s >= 1.34, MutatingAdmissionPolicy is beta
1025+
if versionutils.ConstraintK8sGreaterEqual134.Check(k8sVersion) {
1026+
return "v1beta1"
1027+
}
1028+
1029+
// For older versions, prefer v1beta1 over v1alpha1 when explicitly enabled
1030+
if cluster.Shoot.Spec.Kubernetes.KubeAPIServer != nil &&
1031+
cluster.Shoot.Spec.Kubernetes.KubeAPIServer.RuntimeConfig != nil &&
1032+
cluster.Shoot.Spec.Kubernetes.KubeAPIServer.RuntimeConfig["admissionregistration.k8s.io/v1beta1"] {
1033+
return "v1beta1"
1034+
}
1035+
1036+
return "v1alpha1"
1037+
}

pkg/controller/controlplane/valuesprovider_test.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,4 +722,235 @@ var _ = Describe("ValuesProvider", func() {
722722
Expect(values).NotTo(HaveKey("controller"))
723723
})
724724
})
725+
726+
Describe("#isMutatingAdmissionPolicyEnabled", func() {
727+
var testCluster *extensionscontroller.Cluster
728+
729+
BeforeEach(func() {
730+
calico := "calico"
731+
testCluster = &extensionscontroller.Cluster{
732+
Shoot: &gardencorev1beta1.Shoot{
733+
Spec: gardencorev1beta1.ShootSpec{
734+
Networking: &gardencorev1beta1.Networking{
735+
Type: &calico,
736+
},
737+
Kubernetes: gardencorev1beta1.Kubernetes{
738+
Version: "1.33.0",
739+
},
740+
},
741+
},
742+
}
743+
})
744+
745+
It("should return false if KubeAPIServer is nil", func() {
746+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
747+
})
748+
749+
It("should return false if feature gates are nil", func() {
750+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{}
751+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
752+
})
753+
754+
It("should return false if MutatingAdmissionPolicy feature gate is not set", func() {
755+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
756+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
757+
FeatureGates: map[string]bool{"SomeOtherGate": true},
758+
},
759+
}
760+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
761+
})
762+
763+
It("should return false if MutatingAdmissionPolicy feature gate is disabled", func() {
764+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
765+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
766+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": false},
767+
},
768+
}
769+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
770+
})
771+
772+
It("should return false if RuntimeConfig is nil", func() {
773+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
774+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
775+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": true},
776+
},
777+
}
778+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
779+
})
780+
781+
It("should return false if neither v1alpha1 nor v1beta1 is enabled in RuntimeConfig", func() {
782+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
783+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
784+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": true},
785+
},
786+
RuntimeConfig: map[string]bool{"some.other/v1": true},
787+
}
788+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
789+
})
790+
791+
It("should return true if feature gate is enabled and v1alpha1 is in RuntimeConfig", func() {
792+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
793+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
794+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": true},
795+
},
796+
RuntimeConfig: map[string]bool{"admissionregistration.k8s.io/v1alpha1": true},
797+
}
798+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
799+
})
800+
801+
It("should return true if feature gate is enabled and v1beta1 is in RuntimeConfig", func() {
802+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
803+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
804+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": true},
805+
},
806+
RuntimeConfig: map[string]bool{"admissionregistration.k8s.io/v1beta1": true},
807+
}
808+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
809+
})
810+
811+
It("should return true if feature gate is enabled and both v1alpha1 and v1beta1 are in RuntimeConfig", func() {
812+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
813+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
814+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": true},
815+
},
816+
RuntimeConfig: map[string]bool{
817+
"admissionregistration.k8s.io/v1alpha1": true,
818+
"admissionregistration.k8s.io/v1beta1": true,
819+
},
820+
}
821+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
822+
})
823+
824+
It("should return true for K8s >= 1.34 without any feature gate or RuntimeConfig (beta)", func() {
825+
testCluster.Shoot.Spec.Kubernetes.Version = "1.34.0"
826+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
827+
})
828+
829+
It("should return true for K8s >= 1.34 even without KubeAPIServer config (beta)", func() {
830+
testCluster.Shoot.Spec.Kubernetes.Version = "1.35.0"
831+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
832+
})
833+
834+
It("should return false for K8s >= 1.34 and < 1.36 if feature gate is explicitly disabled (beta)", func() {
835+
testCluster.Shoot.Spec.Kubernetes.Version = "1.34.0"
836+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
837+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
838+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": false},
839+
},
840+
}
841+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
842+
})
843+
844+
It("should return false for K8s 1.35 if feature gate is explicitly disabled (beta)", func() {
845+
testCluster.Shoot.Spec.Kubernetes.Version = "1.35.0"
846+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
847+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
848+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": false},
849+
},
850+
}
851+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeFalse())
852+
})
853+
854+
It("should return true for K8s >= 1.36 even if feature gate is explicitly disabled (GA, locked on)", func() {
855+
testCluster.Shoot.Spec.Kubernetes.Version = "1.36.0"
856+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
857+
KubernetesConfig: gardencorev1beta1.KubernetesConfig{
858+
FeatureGates: map[string]bool{"MutatingAdmissionPolicy": false},
859+
},
860+
}
861+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
862+
})
863+
864+
It("should return true for K8s >= 1.36 without any feature gate or RuntimeConfig (GA)", func() {
865+
testCluster.Shoot.Spec.Kubernetes.Version = "1.36.0"
866+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
867+
})
868+
869+
It("should return true for K8s >= 1.36 even without KubeAPIServer config (GA)", func() {
870+
testCluster.Shoot.Spec.Kubernetes.Version = "1.37.1"
871+
Expect(isMutatingAdmissionPolicyEnabled(testCluster)).To(BeTrue())
872+
})
873+
})
874+
875+
Describe("#mutatingAdmissionPolicyAPIVersion", func() {
876+
var testCluster *extensionscontroller.Cluster
877+
878+
BeforeEach(func() {
879+
testCluster = &extensionscontroller.Cluster{
880+
Shoot: &gardencorev1beta1.Shoot{
881+
Spec: gardencorev1beta1.ShootSpec{
882+
Kubernetes: gardencorev1beta1.Kubernetes{
883+
Version: "1.33.0",
884+
},
885+
},
886+
},
887+
}
888+
})
889+
890+
It("should return v1alpha1 if no RuntimeConfig is set (< 1.34)", func() {
891+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1alpha1"))
892+
})
893+
894+
It("should return v1alpha1 if only v1alpha1 is in RuntimeConfig (< 1.34)", func() {
895+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
896+
RuntimeConfig: map[string]bool{"admissionregistration.k8s.io/v1alpha1": true},
897+
}
898+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1alpha1"))
899+
})
900+
901+
It("should return v1beta1 if v1beta1 is in RuntimeConfig (< 1.34)", func() {
902+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
903+
RuntimeConfig: map[string]bool{"admissionregistration.k8s.io/v1beta1": true},
904+
}
905+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1beta1"))
906+
})
907+
908+
It("should return v1beta1 if both v1alpha1 and v1beta1 are in RuntimeConfig (< 1.34)", func() {
909+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
910+
RuntimeConfig: map[string]bool{
911+
"admissionregistration.k8s.io/v1alpha1": true,
912+
"admissionregistration.k8s.io/v1beta1": true,
913+
},
914+
}
915+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1beta1"))
916+
})
917+
918+
It("should return v1alpha1 if v1beta1 is explicitly disabled in RuntimeConfig (< 1.34)", func() {
919+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
920+
RuntimeConfig: map[string]bool{
921+
"admissionregistration.k8s.io/v1alpha1": true,
922+
"admissionregistration.k8s.io/v1beta1": false,
923+
},
924+
}
925+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1alpha1"))
926+
})
927+
928+
It("should return v1beta1 for K8s >= 1.34 (beta)", func() {
929+
testCluster.Shoot.Spec.Kubernetes.Version = "1.34.0"
930+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1beta1"))
931+
})
932+
933+
It("should return v1beta1 for K8s 1.35 (beta)", func() {
934+
testCluster.Shoot.Spec.Kubernetes.Version = "1.35.0"
935+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1beta1"))
936+
})
937+
938+
It("should return v1 for K8s >= 1.36 (GA)", func() {
939+
testCluster.Shoot.Spec.Kubernetes.Version = "1.36.0"
940+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1"))
941+
})
942+
943+
It("should return v1 for K8s 1.36 even if v1beta1 is in RuntimeConfig", func() {
944+
testCluster.Shoot.Spec.Kubernetes.Version = "1.36.2"
945+
testCluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{
946+
RuntimeConfig: map[string]bool{"admissionregistration.k8s.io/v1beta1": true},
947+
}
948+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1"))
949+
})
950+
951+
It("should return v1 for K8s versions higher than 1.36", func() {
952+
testCluster.Shoot.Spec.Kubernetes.Version = "1.38.0"
953+
Expect(mutatingAdmissionPolicyAPIVersion(testCluster)).To(Equal("v1"))
954+
})
955+
})
725956
})

0 commit comments

Comments
 (0)