Separate feature-gate for AppArmor fields
This commit is contained in:
		| @@ -539,12 +539,14 @@ func dropDisabledFields( | |||||||
| 		podSpec = &api.PodSpec{} | 		podSpec = &api.PodSpec{} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorInUse(oldPodAnnotations, oldPodSpec) { | 	if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorAnnotationsInUse(oldPodAnnotations) { | ||||||
| 		for k := range podAnnotations { | 		for k := range podAnnotations { | ||||||
| 			if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) { | 			if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) { | ||||||
| 				delete(podAnnotations, k) | 				delete(podAnnotations, k) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  | 	if (!utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) || !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields)) && !appArmorFieldsInUse(oldPodSpec) { | ||||||
| 		if podSpec.SecurityContext != nil { | 		if podSpec.SecurityContext != nil { | ||||||
| 			podSpec.SecurityContext.AppArmorProfile = nil | 			podSpec.SecurityContext.AppArmorProfile = nil | ||||||
| 		} | 		} | ||||||
| @@ -947,17 +949,21 @@ func procMountInUse(podSpec *api.PodSpec) bool { | |||||||
| 	return inUse | 	return inUse | ||||||
| } | } | ||||||
|  |  | ||||||
| // appArmorInUse returns true if the pod has apparmor related information | // appArmorAnnotationsInUse returns true if the pod has apparmor annotations | ||||||
| func appArmorInUse(podAnnotations map[string]string, podSpec *api.PodSpec) bool { | func appArmorAnnotationsInUse(podAnnotations map[string]string) bool { | ||||||
| 	if podSpec == nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for k := range podAnnotations { | 	for k := range podAnnotations { | ||||||
| 		if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) { | 		if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // appArmorFieldsInUse returns true if the pod has apparmor fields set | ||||||
|  | func appArmorFieldsInUse(podSpec *api.PodSpec) bool { | ||||||
|  | 	if podSpec == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
| 	if podSpec.SecurityContext != nil && podSpec.SecurityContext.AppArmorProfile != nil { | 	if podSpec.SecurityContext != nil && podSpec.SecurityContext.AppArmorProfile != nil { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -708,18 +708,33 @@ func TestDropProcMount(t *testing.T) { | |||||||
| func TestDropAppArmor(t *testing.T) { | func TestDropAppArmor(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		description    string | 		description    string | ||||||
| 		hasAppArmor bool | 		hasAnnotations bool | ||||||
|  | 		hasFields      bool | ||||||
| 		pod            api.Pod | 		pod            api.Pod | ||||||
| 	}{{ | 	}{{ | ||||||
| 		description:    "with AppArmor Annotations", | 		description:    "with AppArmor Annotations", | ||||||
| 		hasAppArmor: true, | 		hasAnnotations: true, | ||||||
| 		pod: api.Pod{ | 		pod: api.Pod{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}}, | 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}}, | ||||||
| 			Spec:       api.PodSpec{}, | 			Spec:       api.PodSpec{}, | ||||||
| 		}, | 		}, | ||||||
|  | 	}, { | ||||||
|  | 		description:    "with AppArmor Annotations & fields", | ||||||
|  | 		hasAnnotations: true, | ||||||
|  | 		hasFields:      true, | ||||||
|  | 		pod: api.Pod{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}}, | ||||||
|  | 			Spec: api.PodSpec{ | ||||||
|  | 				SecurityContext: &api.PodSecurityContext{ | ||||||
|  | 					AppArmorProfile: &api.AppArmorProfile{ | ||||||
|  | 						Type: api.AppArmorProfileTypeRuntimeDefault, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
| 	}, { | 	}, { | ||||||
| 		description: "with pod AppArmor profile", | 		description: "with pod AppArmor profile", | ||||||
| 		hasAppArmor: true, | 		hasFields:   true, | ||||||
| 		pod: api.Pod{ | 		pod: api.Pod{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, | 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, | ||||||
| 			Spec: api.PodSpec{ | 			Spec: api.PodSpec{ | ||||||
| @@ -732,7 +747,7 @@ func TestDropAppArmor(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	}, { | 	}, { | ||||||
| 		description: "with container AppArmor profile", | 		description: "with container AppArmor profile", | ||||||
| 		hasAppArmor: true, | 		hasFields:   true, | ||||||
| 		pod: api.Pod{ | 		pod: api.Pod{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, | 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, | ||||||
| 			Spec: api.PodSpec{ | 			Spec: api.PodSpec{ | ||||||
| @@ -747,7 +762,6 @@ func TestDropAppArmor(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	}, { | 	}, { | ||||||
| 		description: "without AppArmor", | 		description: "without AppArmor", | ||||||
| 		hasAppArmor: false, |  | ||||||
| 		pod: api.Pod{ | 		pod: api.Pod{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, | 			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}}, | ||||||
| 			Spec:       api.PodSpec{}, | 			Spec:       api.PodSpec{}, | ||||||
| @@ -756,13 +770,18 @@ func TestDropAppArmor(t *testing.T) { | |||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		for _, enabled := range []bool{true, false} { | 		for _, enabled := range []bool{true, false} { | ||||||
| 			t.Run(fmt.Sprintf("%v/enabled=%v", test.description, enabled), func(t *testing.T) { | 			for _, fieldsEnabled := range []bool{true, false} { | ||||||
|  | 				t.Run(fmt.Sprintf("%v/enabled=%v/fields=%v", test.description, enabled, fieldsEnabled), func(t *testing.T) { | ||||||
| 					defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)() | 					defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)() | ||||||
|  | 					defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmorFields, fieldsEnabled)() | ||||||
|  |  | ||||||
| 					newPod := test.pod.DeepCopy() | 					newPod := test.pod.DeepCopy() | ||||||
|  |  | ||||||
| 				if actual := appArmorInUse(newPod.Annotations, &newPod.Spec); actual != test.hasAppArmor { | 					if hasAnnotations := appArmorAnnotationsInUse(newPod.Annotations); hasAnnotations != test.hasAnnotations { | ||||||
| 					t.Errorf("appArmorInUse does not match expectation: %t != %t", actual, test.hasAppArmor) | 						t.Errorf("appArmorAnnotationsInUse does not match expectation: %t != %t", hasAnnotations, test.hasAnnotations) | ||||||
|  | 					} | ||||||
|  | 					if hasFields := appArmorFieldsInUse(&newPod.Spec); hasFields != test.hasFields { | ||||||
|  | 						t.Errorf("appArmorFieldsInUse does not match expectation: %t != %t", hasFields, test.hasFields) | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					DropDisabledPodFields(newPod, newPod) | 					DropDisabledPodFields(newPod, newPod) | ||||||
| @@ -770,22 +789,26 @@ func TestDropAppArmor(t *testing.T) { | |||||||
|  |  | ||||||
| 					DropDisabledPodFields(newPod, nil) | 					DropDisabledPodFields(newPod, nil) | ||||||
|  |  | ||||||
| 				if enabled { | 					if enabled && fieldsEnabled { | ||||||
| 					assert.Equal(t, &test.pod, newPod, "pod should not be mutated when AppArmor is enabled") | 						assert.Equal(t, &test.pod, newPod, "pod should not be mutated when both feature gates are enabled") | ||||||
| 				} else { | 						return | ||||||
| 					if appArmorInUse(newPod.Annotations, &newPod.Spec) { |  | ||||||
| 						t.Errorf("newPod should not be using appArmor after dropping disabled fields") |  | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					if test.hasAppArmor { | 					expectAnnotations := test.hasAnnotations && enabled | ||||||
| 						assert.NotEqual(t, &test.pod, newPod, "pod should be mutated to drop AppArmor") | 					assert.Equal(t, expectAnnotations, appArmorAnnotationsInUse(newPod.Annotations), "AppArmor annotations expectation") | ||||||
| 					} else { | 					if expectAnnotations == test.hasAnnotations { | ||||||
| 						assert.Equal(t, &test.pod, newPod, "pod without AppArmor should not be mutated") | 						assert.Equal(t, test.pod.Annotations, newPod.Annotations, "annotations should not be mutated") | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | 					expectFields := test.hasFields && enabled && fieldsEnabled | ||||||
|  | 					assert.Equal(t, expectFields, appArmorFieldsInUse(&newPod.Spec), "AppArmor fields expectation") | ||||||
|  | 					if expectFields == test.hasFields { | ||||||
|  | 						assert.Equal(t, &test.pod.Spec, &newPod.Spec, "PodSpec should not be mutated") | ||||||
| 					} | 					} | ||||||
| 				}) | 				}) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestDropDynamicResourceAllocation(t *testing.T) { | func TestDropDynamicResourceAllocation(t *testing.T) { | ||||||
|   | |||||||
| @@ -4736,6 +4736,9 @@ func ValidateAppArmorProfileFormat(profile string) error { | |||||||
|  |  | ||||||
| // validateAppArmorAnnotationsAndFieldsMatchOnCreate validates that AppArmor fields and annotations are consistent. | // validateAppArmorAnnotationsAndFieldsMatchOnCreate validates that AppArmor fields and annotations are consistent. | ||||||
| func validateAppArmorAnnotationsAndFieldsMatchOnCreate(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList { | func validateAppArmorAnnotationsAndFieldsMatchOnCreate(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList { | ||||||
|  | 	if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| 	if podSpec.OS != nil && podSpec.OS.Name == core.Windows { | 	if podSpec.OS != nil && podSpec.OS.Name == core.Windows { | ||||||
| 		// Skip consistency check for windows pods. | 		// Skip consistency check for windows pods. | ||||||
| 		return nil | 		return nil | ||||||
|   | |||||||
| @@ -63,6 +63,10 @@ const ( | |||||||
| 	// beta: v1.4 | 	// beta: v1.4 | ||||||
| 	AppArmor featuregate.Feature = "AppArmor" | 	AppArmor featuregate.Feature = "AppArmor" | ||||||
|  |  | ||||||
|  | 	// owner: @tallclair | ||||||
|  | 	// beta: v1.30 | ||||||
|  | 	AppArmorFields featuregate.Feature = "AppArmorFields" | ||||||
|  |  | ||||||
| 	// owner: @danwinship | 	// owner: @danwinship | ||||||
| 	// alpha: v1.27 | 	// alpha: v1.27 | ||||||
| 	// beta: v1.29 | 	// beta: v1.29 | ||||||
| @@ -975,6 +979,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS | |||||||
|  |  | ||||||
| 	AppArmor: {Default: true, PreRelease: featuregate.Beta}, | 	AppArmor: {Default: true, PreRelease: featuregate.Beta}, | ||||||
|  |  | ||||||
|  | 	AppArmorFields: {Default: true, PreRelease: featuregate.Beta}, | ||||||
|  |  | ||||||
| 	CloudDualStackNodeIPs: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.32 | 	CloudDualStackNodeIPs: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.32 | ||||||
|  |  | ||||||
| 	ClusterTrustBundle: {Default: false, PreRelease: featuregate.Alpha}, | 	ClusterTrustBundle: {Default: false, PreRelease: featuregate.Alpha}, | ||||||
|   | |||||||
| @@ -764,6 +764,10 @@ func applySchedulingGatedCondition(pod *api.Pod) { | |||||||
| // applyAppArmorVersionSkew implements the version skew behavior described in: | // applyAppArmorVersionSkew implements the version skew behavior described in: | ||||||
| // https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/24-apparmor#version-skew-strategy | // https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/24-apparmor#version-skew-strategy | ||||||
| func applyAppArmorVersionSkew(pod *api.Pod) { | func applyAppArmorVersionSkew(pod *api.Pod) { | ||||||
|  | 	if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if pod.Spec.OS != nil && pod.Spec.OS.Name == api.Windows { | 	if pod.Spec.OS != nil && pod.Spec.OS.Name == api.Windows { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -20,7 +20,9 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	v1 "k8s.io/api/core/v1" | 	v1 "k8s.io/api/core/v1" | ||||||
|  | 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||||
| 	podutil "k8s.io/kubernetes/pkg/api/v1/pod" | 	podutil "k8s.io/kubernetes/pkg/api/v1/pod" | ||||||
|  | 	"k8s.io/kubernetes/pkg/features" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Checks whether app armor is required for the pod to run. AppArmor is considered required if any | // Checks whether app armor is required for the pod to run. AppArmor is considered required if any | ||||||
| @@ -52,6 +54,10 @@ func isRequired(pod *v1.Pod) bool { | |||||||
|  |  | ||||||
| // GetProfileName returns the name of the profile to use with the container. | // GetProfileName returns the name of the profile to use with the container. | ||||||
| func GetProfile(pod *v1.Pod, container *v1.Container) *v1.AppArmorProfile { | func GetProfile(pod *v1.Pod, container *v1.Container) *v1.AppArmorProfile { | ||||||
|  | 	if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) { | ||||||
|  | 		return getProfileFromPodAnnotations(pod.Annotations, container.Name) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if container.SecurityContext != nil && container.SecurityContext.AppArmorProfile != nil { | 	if container.SecurityContext != nil && container.SecurityContext.AppArmorProfile != nil { | ||||||
| 		return container.SecurityContext.AppArmorProfile | 		return container.SecurityContext.AppArmorProfile | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Tim Allclair
					Tim Allclair