Skip to content

Commit 2d86cbf

Browse files
committed
Separate feature-gate for AppArmor fields
1 parent 22068e0 commit 2d86cbf

File tree

6 files changed

+83
-35
lines changed

6 files changed

+83
-35
lines changed

pkg/api/pod/util.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,14 @@ func dropDisabledFields(
539539
podSpec = &api.PodSpec{}
540540
}
541541

542-
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorInUse(oldPodAnnotations, oldPodSpec) {
542+
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorAnnotationsInUse(oldPodAnnotations) {
543543
for k := range podAnnotations {
544544
if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) {
545545
delete(podAnnotations, k)
546546
}
547547
}
548+
}
549+
if (!utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) || !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields)) && !appArmorFieldsInUse(oldPodSpec) {
548550
if podSpec.SecurityContext != nil {
549551
podSpec.SecurityContext.AppArmorProfile = nil
550552
}
@@ -947,17 +949,21 @@ func procMountInUse(podSpec *api.PodSpec) bool {
947949
return inUse
948950
}
949951

950-
// appArmorInUse returns true if the pod has apparmor related information
951-
func appArmorInUse(podAnnotations map[string]string, podSpec *api.PodSpec) bool {
952-
if podSpec == nil {
953-
return false
954-
}
955-
952+
// appArmorAnnotationsInUse returns true if the pod has apparmor annotations
953+
func appArmorAnnotationsInUse(podAnnotations map[string]string) bool {
956954
for k := range podAnnotations {
957955
if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) {
958956
return true
959957
}
960958
}
959+
return false
960+
}
961+
962+
// appArmorFieldsInUse returns true if the pod has apparmor fields set
963+
func appArmorFieldsInUse(podSpec *api.PodSpec) bool {
964+
if podSpec == nil {
965+
return false
966+
}
961967
if podSpec.SecurityContext != nil && podSpec.SecurityContext.AppArmorProfile != nil {
962968
return true
963969
}

pkg/api/pod/util_test.go

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -707,19 +707,34 @@ func TestDropProcMount(t *testing.T) {
707707

708708
func TestDropAppArmor(t *testing.T) {
709709
tests := []struct {
710-
description string
711-
hasAppArmor bool
712-
pod api.Pod
710+
description string
711+
hasAnnotations bool
712+
hasFields bool
713+
pod api.Pod
713714
}{{
714-
description: "with AppArmor Annotations",
715-
hasAppArmor: true,
715+
description: "with AppArmor Annotations",
716+
hasAnnotations: true,
716717
pod: api.Pod{
717718
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}},
718719
Spec: api.PodSpec{},
719720
},
721+
}, {
722+
description: "with AppArmor Annotations & fields",
723+
hasAnnotations: true,
724+
hasFields: true,
725+
pod: api.Pod{
726+
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}},
727+
Spec: api.PodSpec{
728+
SecurityContext: &api.PodSecurityContext{
729+
AppArmorProfile: &api.AppArmorProfile{
730+
Type: api.AppArmorProfileTypeRuntimeDefault,
731+
},
732+
},
733+
},
734+
},
720735
}, {
721736
description: "with pod AppArmor profile",
722-
hasAppArmor: true,
737+
hasFields: true,
723738
pod: api.Pod{
724739
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
725740
Spec: api.PodSpec{
@@ -732,7 +747,7 @@ func TestDropAppArmor(t *testing.T) {
732747
},
733748
}, {
734749
description: "with container AppArmor profile",
735-
hasAppArmor: true,
750+
hasFields: true,
736751
pod: api.Pod{
737752
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
738753
Spec: api.PodSpec{
@@ -747,7 +762,6 @@ func TestDropAppArmor(t *testing.T) {
747762
},
748763
}, {
749764
description: "without AppArmor",
750-
hasAppArmor: false,
751765
pod: api.Pod{
752766
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
753767
Spec: api.PodSpec{},
@@ -756,34 +770,43 @@ func TestDropAppArmor(t *testing.T) {
756770

757771
for _, test := range tests {
758772
for _, enabled := range []bool{true, false} {
759-
t.Run(fmt.Sprintf("%v/enabled=%v", test.description, enabled), func(t *testing.T) {
760-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)()
773+
for _, fieldsEnabled := range []bool{true, false} {
774+
t.Run(fmt.Sprintf("%v/enabled=%v/fields=%v", test.description, enabled, fieldsEnabled), func(t *testing.T) {
775+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)()
776+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmorFields, fieldsEnabled)()
761777

762-
newPod := test.pod.DeepCopy()
778+
newPod := test.pod.DeepCopy()
763779

764-
if actual := appArmorInUse(newPod.Annotations, &newPod.Spec); actual != test.hasAppArmor {
765-
t.Errorf("appArmorInUse does not match expectation: %t != %t", actual, test.hasAppArmor)
766-
}
780+
if hasAnnotations := appArmorAnnotationsInUse(newPod.Annotations); hasAnnotations != test.hasAnnotations {
781+
t.Errorf("appArmorAnnotationsInUse does not match expectation: %t != %t", hasAnnotations, test.hasAnnotations)
782+
}
783+
if hasFields := appArmorFieldsInUse(&newPod.Spec); hasFields != test.hasFields {
784+
t.Errorf("appArmorFieldsInUse does not match expectation: %t != %t", hasFields, test.hasFields)
785+
}
767786

768-
DropDisabledPodFields(newPod, newPod)
769-
require.Equal(t, &test.pod, newPod, "unchanged pod should never be mutated")
787+
DropDisabledPodFields(newPod, newPod)
788+
require.Equal(t, &test.pod, newPod, "unchanged pod should never be mutated")
770789

771-
DropDisabledPodFields(newPod, nil)
790+
DropDisabledPodFields(newPod, nil)
772791

773-
if enabled {
774-
assert.Equal(t, &test.pod, newPod, "pod should not be mutated when AppArmor is enabled")
775-
} else {
776-
if appArmorInUse(newPod.Annotations, &newPod.Spec) {
777-
t.Errorf("newPod should not be using appArmor after dropping disabled fields")
792+
if enabled && fieldsEnabled {
793+
assert.Equal(t, &test.pod, newPod, "pod should not be mutated when both feature gates are enabled")
794+
return
778795
}
779796

780-
if test.hasAppArmor {
781-
assert.NotEqual(t, &test.pod, newPod, "pod should be mutated to drop AppArmor")
782-
} else {
783-
assert.Equal(t, &test.pod, newPod, "pod without AppArmor should not be mutated")
797+
expectAnnotations := test.hasAnnotations && enabled
798+
assert.Equal(t, expectAnnotations, appArmorAnnotationsInUse(newPod.Annotations), "AppArmor annotations expectation")
799+
if expectAnnotations == test.hasAnnotations {
800+
assert.Equal(t, test.pod.Annotations, newPod.Annotations, "annotations should not be mutated")
784801
}
785-
}
786-
})
802+
803+
expectFields := test.hasFields && enabled && fieldsEnabled
804+
assert.Equal(t, expectFields, appArmorFieldsInUse(&newPod.Spec), "AppArmor fields expectation")
805+
if expectFields == test.hasFields {
806+
assert.Equal(t, &test.pod.Spec, &newPod.Spec, "PodSpec should not be mutated")
807+
}
808+
})
809+
}
787810
}
788811
}
789812
}

pkg/apis/core/validation/validation.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4736,6 +4736,9 @@ func ValidateAppArmorProfileFormat(profile string) error {
47364736

47374737
// validateAppArmorAnnotationsAndFieldsMatchOnCreate validates that AppArmor fields and annotations are consistent.
47384738
func validateAppArmorAnnotationsAndFieldsMatchOnCreate(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList {
4739+
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
4740+
return nil
4741+
}
47394742
if podSpec.OS != nil && podSpec.OS.Name == core.Windows {
47404743
// Skip consistency check for windows pods.
47414744
return nil

pkg/features/kube_features.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ const (
6363
// beta: v1.4
6464
AppArmor featuregate.Feature = "AppArmor"
6565

66+
// owner: @tallclair
67+
// beta: v1.30
68+
AppArmorFields featuregate.Feature = "AppArmorFields"
69+
6670
// owner: @danwinship
6771
// alpha: v1.27
6872
// beta: v1.29
@@ -975,6 +979,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
975979

976980
AppArmor: {Default: true, PreRelease: featuregate.Beta},
977981

982+
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
983+
978984
CloudDualStackNodeIPs: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.32
979985

980986
ClusterTrustBundle: {Default: false, PreRelease: featuregate.Alpha},

pkg/registry/core/pod/strategy.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,10 @@ func applySchedulingGatedCondition(pod *api.Pod) {
764764
// applyAppArmorVersionSkew implements the version skew behavior described in:
765765
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/24-apparmor#version-skew-strategy
766766
func applyAppArmorVersionSkew(pod *api.Pod) {
767+
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
768+
return
769+
}
770+
767771
if pod.Spec.OS != nil && pod.Spec.OS.Name == api.Windows {
768772
return
769773
}

pkg/security/apparmor/helpers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"strings"
2121

2222
v1 "k8s.io/api/core/v1"
23+
utilfeature "k8s.io/apiserver/pkg/util/feature"
2324
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
25+
"k8s.io/kubernetes/pkg/features"
2426
)
2527

2628
// 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 {
5254

5355
// GetProfileName returns the name of the profile to use with the container.
5456
func GetProfile(pod *v1.Pod, container *v1.Container) *v1.AppArmorProfile {
57+
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
58+
return getProfileFromPodAnnotations(pod.Annotations, container.Name)
59+
}
60+
5561
if container.SecurityContext != nil && container.SecurityContext.AppArmorProfile != nil {
5662
return container.SecurityContext.AppArmorProfile
5763
}

0 commit comments

Comments
 (0)