mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-07 11:13:48 +00:00
Implement version skew strategy
This commit is contained in:
parent
b7f620c12b
commit
289ec02e8b
@ -4729,6 +4729,66 @@ func ValidateAppArmorProfileFormat(profile string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateAppArmorAnnotationsAndFields validates that AppArmor fields and annotations are consistent.
|
||||||
|
func validateAppArmorAnnotationsAndFields(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList {
|
||||||
|
if podSpec.OS != nil && podSpec.OS.Name == core.Windows {
|
||||||
|
// Skip consistency check for windows pods.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
podshelper.VisitContainersWithPath(podSpec, specPath, func(c *core.Container, cFldPath *field.Path) bool {
|
||||||
|
var field *core.AppArmorProfile
|
||||||
|
if c.SecurityContext != nil {
|
||||||
|
field = c.SecurityContext.AppArmorProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
if field == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
key := core.AppArmorContainerAnnotationKeyPrefix + c.Name
|
||||||
|
if annotation, found := objectMeta.Annotations[key]; found {
|
||||||
|
apparmorPath := cFldPath.Child("securityContext").Child("appArmorProfile")
|
||||||
|
err := validateAppArmorAnnotationsAndFieldsMatch(annotation, field, apparmorPath)
|
||||||
|
if err != nil {
|
||||||
|
allErrs = append(allErrs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAppArmorAnnotationsAndFieldsMatch(annotationValue string, apparmorField *core.AppArmorProfile, fldPath *field.Path) *field.Error {
|
||||||
|
if apparmorField == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch apparmorField.Type {
|
||||||
|
case core.AppArmorProfileTypeUnconfined:
|
||||||
|
if annotationValue != core.AppArmorProfileNameUnconfined {
|
||||||
|
return field.Forbidden(fldPath.Child("type"), "apparmor type in annotation and field must match")
|
||||||
|
}
|
||||||
|
|
||||||
|
case core.AppArmorProfileTypeRuntimeDefault:
|
||||||
|
if annotationValue != core.AppArmorProfileRuntimeDefault {
|
||||||
|
return field.Forbidden(fldPath.Child("type"), "apparmor type in annotation and field must match")
|
||||||
|
}
|
||||||
|
|
||||||
|
case core.AppArmorProfileTypeLocalhost:
|
||||||
|
if !strings.HasPrefix(annotationValue, core.AppArmorProfileLocalhostPrefix) {
|
||||||
|
return field.Forbidden(fldPath.Child("type"), "apparmor type in annotation and field must match")
|
||||||
|
} else if apparmorField.LocalhostProfile == nil || strings.TrimPrefix(annotationValue, core.AppArmorProfileLocalhostPrefix) != *apparmorField.LocalhostProfile {
|
||||||
|
return field.Forbidden(fldPath.Child("localhostProfile"), "apparmor profile in annotation and field must match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func podSpecHasContainer(spec *core.PodSpec, containerName string) bool {
|
func podSpecHasContainer(spec *core.PodSpec, containerName string) bool {
|
||||||
var hasContainer bool
|
var hasContainer bool
|
||||||
podshelper.VisitContainersWithPath(spec, field.NewPath("spec"), func(c *core.Container, _ *field.Path) bool {
|
podshelper.VisitContainersWithPath(spec, field.NewPath("spec"), func(c *core.Container, _ *field.Path) bool {
|
||||||
@ -4883,6 +4943,7 @@ func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList
|
|||||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared"))
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared"))
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
|
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
|
||||||
|
allErrs = append(allErrs, validateAppArmorAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
@ -10425,6 +10425,27 @@ func TestValidatePod(t *testing.T) {
|
|||||||
DNSPolicy: core.DNSDefault,
|
DNSPolicy: core.DNSDefault,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"matching AppArmor fields and annotations": {
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "123",
|
||||||
|
Namespace: "ns",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
core.AppArmorContainerAnnotationKeyPrefix + "ctr": core.AppArmorProfileLocalhostPrefix + "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
SecurityContext: &core.SecurityContext{
|
||||||
|
AppArmorProfile: &core.AppArmorProfile{
|
||||||
|
Type: core.AppArmorProfileTypeLocalhost,
|
||||||
|
LocalhostProfile: ptr.To("foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
"syntactically valid sysctls": {
|
"syntactically valid sysctls": {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "123",
|
Name: "123",
|
||||||
@ -12118,6 +12139,53 @@ func TestValidatePod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"mismatched AppArmor field and annotation types": {
|
||||||
|
expectedError: "Forbidden: apparmor type in annotation and field must match",
|
||||||
|
spec: core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "123",
|
||||||
|
Namespace: "ns",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
core.AppArmorContainerAnnotationKeyPrefix + "ctr": core.AppArmorProfileRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
SecurityContext: &core.SecurityContext{
|
||||||
|
AppArmorProfile: &core.AppArmorProfile{
|
||||||
|
Type: core.AppArmorProfileTypeUnconfined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"mismatched AppArmor localhost profiles": {
|
||||||
|
expectedError: "Forbidden: apparmor profile in annotation and field must match",
|
||||||
|
spec: core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "123",
|
||||||
|
Namespace: "ns",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
core.AppArmorContainerAnnotationKeyPrefix + "ctr": core.AppArmorProfileLocalhostPrefix + "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
SecurityContext: &core.SecurityContext{
|
||||||
|
AppArmorProfile: &core.AppArmorProfile{
|
||||||
|
Type: core.AppArmorProfileTypeLocalhost,
|
||||||
|
LocalhostProfile: ptr.To("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"invalid extended resource name in container request": {
|
"invalid extended resource name in container request": {
|
||||||
expectedError: "must be a standard resource for containers",
|
expectedError: "must be a standard resource for containers",
|
||||||
spec: core.Pod{
|
spec: core.Pod{
|
||||||
|
@ -91,6 +91,7 @@ func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
|||||||
|
|
||||||
applySchedulingGatedCondition(pod)
|
applySchedulingGatedCondition(pod)
|
||||||
mutatePodAffinity(pod)
|
mutatePodAffinity(pod)
|
||||||
|
applyAppArmorVersionSkew(pod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||||
@ -758,3 +759,109 @@ func applySchedulingGatedCondition(pod *api.Pod) {
|
|||||||
Message: "Scheduling is blocked due to non-empty scheduling gates",
|
Message: "Scheduling is blocked due to non-empty scheduling gates",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyAppArmorVersionSkew implements the version skew behavior described in:
|
||||||
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/24-apparmor#version-skew-strategy
|
||||||
|
func applyAppArmorVersionSkew(pod *api.Pod) {
|
||||||
|
if pod.Spec.OS != nil && pod.Spec.OS.Name == api.Windows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var podProfile *api.AppArmorProfile
|
||||||
|
if pod.Spec.SecurityContext != nil {
|
||||||
|
podProfile = pod.Spec.SecurityContext.AppArmorProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the containers of the pod
|
||||||
|
podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(),
|
||||||
|
func(ctr *api.Container, _ podutil.ContainerType) bool {
|
||||||
|
// get possible annotation and field
|
||||||
|
key := api.AppArmorContainerAnnotationKeyPrefix + ctr.Name
|
||||||
|
annotation, hasAnnotation := pod.Annotations[key]
|
||||||
|
|
||||||
|
field, hasField := (*api.AppArmorProfile)(nil), false
|
||||||
|
if ctr.SecurityContext != nil && ctr.SecurityContext.AppArmorProfile != nil {
|
||||||
|
field = ctr.SecurityContext.AppArmorProfile
|
||||||
|
hasField = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync field and annotation
|
||||||
|
if !hasAnnotation {
|
||||||
|
newAnnotation := ""
|
||||||
|
if hasField {
|
||||||
|
newAnnotation = appArmorAnnotationForField(field)
|
||||||
|
} else if podProfile != nil {
|
||||||
|
newAnnotation = appArmorAnnotationForField(podProfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newAnnotation != "" {
|
||||||
|
if pod.Annotations == nil {
|
||||||
|
pod.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
pod.Annotations[key] = newAnnotation
|
||||||
|
}
|
||||||
|
} else if !hasField {
|
||||||
|
newField := apparmorFieldForAnnotation(annotation)
|
||||||
|
|
||||||
|
if newField != nil {
|
||||||
|
if ctr.SecurityContext == nil {
|
||||||
|
ctr.SecurityContext = &api.SecurityContext{}
|
||||||
|
}
|
||||||
|
ctr.SecurityContext.AppArmorProfile = newField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// appArmorFieldForAnnotation takes a pod apparmor profile field and returns the
|
||||||
|
// converted annotation value
|
||||||
|
func appArmorAnnotationForField(field *api.AppArmorProfile) string {
|
||||||
|
// If only apparmor fields are specified, add the corresponding annotations.
|
||||||
|
// This ensures that the fields are enforced even if the node version
|
||||||
|
// trails the API version
|
||||||
|
switch field.Type {
|
||||||
|
case api.AppArmorProfileTypeUnconfined:
|
||||||
|
return api.AppArmorProfileNameUnconfined
|
||||||
|
|
||||||
|
case api.AppArmorProfileTypeRuntimeDefault:
|
||||||
|
return api.AppArmorProfileRuntimeDefault
|
||||||
|
|
||||||
|
case api.AppArmorProfileTypeLocalhost:
|
||||||
|
if field.LocalhostProfile != nil {
|
||||||
|
return api.AppArmorProfileLocalhostPrefix + *field.LocalhostProfile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can only reach this code path if the LocalhostProfile is nil but the
|
||||||
|
// provided field type is AppArmorProfileTypeLocalhost or if an unrecognized
|
||||||
|
// type is specified
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// apparmorFieldForAnnotation takes a pod annotation and returns the converted
|
||||||
|
// apparmor profile field.
|
||||||
|
func apparmorFieldForAnnotation(annotation string) *api.AppArmorProfile {
|
||||||
|
if annotation == api.AppArmorProfileNameUnconfined {
|
||||||
|
return &api.AppArmorProfile{Type: api.AppArmorProfileTypeUnconfined}
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotation == api.AppArmorProfileRuntimeDefault {
|
||||||
|
return &api.AppArmorProfile{Type: api.AppArmorProfileTypeRuntimeDefault}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(annotation, api.AppArmorProfileLocalhostPrefix) {
|
||||||
|
localhostProfile := strings.TrimPrefix(annotation, api.AppArmorProfileLocalhostPrefix)
|
||||||
|
if localhostProfile != "" {
|
||||||
|
return &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeLocalhost,
|
||||||
|
LocalhostProfile: &localhostProfile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can only reach this code path if the localhostProfile name has a zero
|
||||||
|
// length or if the annotation has an unrecognized value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
@ -2106,3 +2107,431 @@ func TestPodLifecycleSleepActionEnablement(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyAppArmorVersionSkew(t *testing.T) {
|
||||||
|
testProfile := "test"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
pod *api.Pod
|
||||||
|
validation func(*testing.T, *api.Pod)
|
||||||
|
}{{
|
||||||
|
description: "Security context nil",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
InitContainers: []api.Container{{Name: "init"}},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Empty(t, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Security context not nil",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{},
|
||||||
|
InitContainers: []api.Container{{Name: "init"}},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Empty(t, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext.AppArmorProfile)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Field type unconfined and no annotation present",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeUnconfined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InitContainers: []api.Container{{Name: "init"}},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "init": api.AppArmorProfileNameUnconfined,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileNameUnconfined,
|
||||||
|
}, pod.Annotations)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Field type default and no annotation present",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InitContainers: []api.Container{{Name: "init"}},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "init": api.AppArmorProfileRuntimeDefault,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileRuntimeDefault,
|
||||||
|
}, pod.Annotations)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Field type localhost and no annotation present",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeLocalhost,
|
||||||
|
LocalhostProfile: &testProfile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InitContainers: []api.Container{{Name: "init"}},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "init": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
}, pod.Annotations)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Field type localhost but profile is nil",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeLocalhost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InitContainers: []api.Container{{Name: "init"}},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Len(t, pod.Annotations, 0)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Security context not nil (container)",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "ctr",
|
||||||
|
SecurityContext: &api.SecurityContext{},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Len(t, pod.Annotations, 0)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Field type RuntimeDefault and no annotation present (container)",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "ctr",
|
||||||
|
SecurityContext: &api.SecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileRuntimeDefault,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Field type localhost and no annotation present (container)",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "ctr",
|
||||||
|
SecurityContext: &api.SecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeLocalhost,
|
||||||
|
LocalhostProfile: &testProfile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Container overrides pod profile",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "ctr",
|
||||||
|
SecurityContext: &api.SecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeUnconfined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileNameUnconfined,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Multiple containers with fields (container)",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
InitContainers: []api.Container{{
|
||||||
|
Name: "init",
|
||||||
|
SecurityContext: &api.SecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeLocalhost,
|
||||||
|
LocalhostProfile: &testProfile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "a",
|
||||||
|
SecurityContext: &api.SecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeUnconfined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "b",
|
||||||
|
}, {
|
||||||
|
Name: "c",
|
||||||
|
SecurityContext: &api.SecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "init": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "a": api.AppArmorProfileNameUnconfined,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "c": api.AppArmorProfileRuntimeDefault,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[1].SecurityContext)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[2].SecurityContext.AppArmorProfile.Type)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Annotation 'unconfined' and no field present (container)",
|
||||||
|
pod: &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileNameUnconfined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileNameUnconfined,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Annotation for non-existent container",
|
||||||
|
pod: &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "foo-bar": api.AppArmorProfileNameUnconfined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "foo-bar": api.AppArmorProfileNameUnconfined,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Annotation 'runtime/default' and no field present (container)",
|
||||||
|
pod: &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeUnconfined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "ctr",
|
||||||
|
SecurityContext: &api.SecurityContext{},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileRuntimeDefault,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.SecurityContext.AppArmorProfile.Type)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Multiple containers by annotations (container)",
|
||||||
|
pod: &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "init": api.AppArmorProfileNameUnconfined,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "a": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "c": api.AppArmorProfileRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InitContainers: []api.Container{{Name: "init"}},
|
||||||
|
Containers: []api.Container{
|
||||||
|
{Name: "a"},
|
||||||
|
{Name: "b"},
|
||||||
|
{Name: "c"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "init": api.AppArmorProfileNameUnconfined,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "a": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "b": api.AppArmorProfileRuntimeDefault,
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "c": api.AppArmorProfileRuntimeDefault,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Equal(t, testProfile, *pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[1].SecurityContext)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[2].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Conflicting field and annotations",
|
||||||
|
pod: &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{{
|
||||||
|
Name: "ctr",
|
||||||
|
SecurityContext: &api.SecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileLocalhostPrefix + testProfile,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Invalid annotation value",
|
||||||
|
pod: &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": "not-a-real-type",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": "not-a-real-type",
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
|
||||||
|
assert.Nil(t, pod.Spec.SecurityContext)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Invalid field type",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
AppArmorProfile: &api.AppArmorProfile{
|
||||||
|
Type: "invalid-type",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Empty(t, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
description: "Ignore annotations on windows",
|
||||||
|
pod: &api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileRuntimeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
OS: &api.PodOS{Name: api.Windows},
|
||||||
|
Containers: []api.Container{{Name: "ctr"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validation: func(t *testing.T, pod *api.Pod) {
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
api.AppArmorContainerAnnotationKeyPrefix + "ctr": api.AppArmorProfileRuntimeDefault,
|
||||||
|
}, pod.Annotations)
|
||||||
|
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
applyAppArmorVersionSkew(test.pod)
|
||||||
|
test.validation(t, test.pod)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user