Skip to content

Commit 26f0227

Browse files
authored
Merge pull request kubernetes#91408 from saschagrunert/seccomp-api-migration
Add seccomp GA version skew for pods
2 parents 2d327ac + c3ba2d8 commit 26f0227

File tree

3 files changed

+539
-0
lines changed

3 files changed

+539
-0
lines changed

pkg/registry/core/pod/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ go_library(
2222
"//pkg/features:go_default_library",
2323
"//pkg/kubelet/client:go_default_library",
2424
"//pkg/proxy/util:go_default_library",
25+
"//staging/src/k8s.io/api/core/v1:go_default_library",
2526
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
2627
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
2728
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
@@ -49,6 +50,7 @@ go_test(
4950
"//pkg/apis/core/install:go_default_library",
5051
"//pkg/features:go_default_library",
5152
"//pkg/kubelet/client:go_default_library",
53+
"//staging/src/k8s.io/api/core/v1:go_default_library",
5254
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
5355
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
5456
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
@@ -60,6 +62,7 @@ go_test(
6062
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
6163
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
6264
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
65+
"//vendor/github.com/stretchr/testify/require:go_default_library",
6366
],
6467
)
6568

pkg/registry/core/pod/strategy.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"strings"
2727
"time"
2828

29+
"k8s.io/api/core/v1"
2930
"k8s.io/apimachinery/pkg/api/errors"
3031
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132
"k8s.io/apimachinery/pkg/fields"
@@ -74,6 +75,8 @@ func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
7475
}
7576

7677
podutil.DropDisabledPodFields(pod, nil)
78+
79+
applySeccompVersionSkew(pod)
7780
}
7881

7982
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
@@ -569,3 +572,129 @@ func validateContainer(container string, pod *api.Pod) (string, error) {
569572

570573
return container, nil
571574
}
575+
576+
// applySeccompVersionSkew implements the version skew behavior described in:
577+
// https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/20190717-seccomp-ga.md#version-skew-strategy
578+
func applySeccompVersionSkew(pod *api.Pod) {
579+
// get possible annotation and field
580+
annotation, hasAnnotation := pod.Annotations[v1.SeccompPodAnnotationKey]
581+
field, hasField := (*api.SeccompProfile)(nil), false
582+
583+
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil {
584+
field = pod.Spec.SecurityContext.SeccompProfile
585+
hasField = true
586+
}
587+
588+
// sync field and annotation
589+
if hasField && !hasAnnotation {
590+
newAnnotation := seccompAnnotationForField(field)
591+
592+
if newAnnotation != "" {
593+
if pod.Annotations == nil {
594+
pod.Annotations = map[string]string{}
595+
}
596+
pod.Annotations[v1.SeccompPodAnnotationKey] = newAnnotation
597+
}
598+
} else if hasAnnotation && !hasField {
599+
newField := seccompFieldForAnnotation(annotation)
600+
601+
if newField != nil {
602+
if pod.Spec.SecurityContext == nil {
603+
pod.Spec.SecurityContext = &api.PodSecurityContext{}
604+
}
605+
pod.Spec.SecurityContext.SeccompProfile = newField
606+
}
607+
}
608+
609+
// Handle the containers of the pod
610+
podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(),
611+
func(ctr *api.Container, _ podutil.ContainerType) bool {
612+
// get possible annotation and field
613+
key := api.SeccompContainerAnnotationKeyPrefix + ctr.Name
614+
annotation, hasAnnotation := pod.Annotations[key]
615+
616+
field, hasField := (*api.SeccompProfile)(nil), false
617+
if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil {
618+
field = ctr.SecurityContext.SeccompProfile
619+
hasField = true
620+
}
621+
622+
// sync field and annotation
623+
if hasField && !hasAnnotation {
624+
newAnnotation := seccompAnnotationForField(field)
625+
626+
if newAnnotation != "" {
627+
if pod.Annotations == nil {
628+
pod.Annotations = map[string]string{}
629+
}
630+
pod.Annotations[key] = newAnnotation
631+
}
632+
} else if hasAnnotation && !hasField {
633+
newField := seccompFieldForAnnotation(annotation)
634+
635+
if newField != nil {
636+
if ctr.SecurityContext == nil {
637+
ctr.SecurityContext = &api.SecurityContext{}
638+
}
639+
ctr.SecurityContext.SeccompProfile = newField
640+
}
641+
}
642+
643+
return true
644+
})
645+
}
646+
647+
// seccompFieldForAnnotation takes a pod seccomp profile field and returns the
648+
// converted annotation value
649+
func seccompAnnotationForField(field *api.SeccompProfile) string {
650+
// If only seccomp fields are specified, add the corresponding annotations.
651+
// This ensures that the fields are enforced even if the node version
652+
// trails the API version
653+
switch field.Type {
654+
case api.SeccompProfileTypeUnconfined:
655+
return v1.SeccompProfileNameUnconfined
656+
657+
case api.SeccompProfileTypeRuntimeDefault:
658+
return v1.SeccompProfileRuntimeDefault
659+
660+
case api.SeccompProfileTypeLocalhost:
661+
if field.LocalhostProfile != nil {
662+
return v1.SeccompLocalhostProfileNamePrefix + *field.LocalhostProfile
663+
}
664+
}
665+
666+
// we can only reach this code path if the LocalhostProfile is nil but the
667+
// provided field type is SeccompProfileTypeLocalhost or if an unrecognized
668+
// type is specified
669+
return ""
670+
}
671+
672+
// seccompFieldForAnnotation takes a pod annotation and returns the converted
673+
// seccomp profile field.
674+
func seccompFieldForAnnotation(annotation string) *api.SeccompProfile {
675+
// If only seccomp annotations are specified, copy the values into the
676+
// corresponding fields. This ensures that existing applications continue
677+
// to enforce seccomp, and prevents the kubelet from needing to resolve
678+
// annotations & fields.
679+
if annotation == v1.SeccompProfileNameUnconfined {
680+
return &api.SeccompProfile{Type: api.SeccompProfileTypeUnconfined}
681+
}
682+
683+
if annotation == api.SeccompProfileRuntimeDefault || annotation == api.DeprecatedSeccompProfileDockerDefault {
684+
return &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault}
685+
}
686+
687+
if strings.HasPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix) {
688+
localhostProfile := strings.TrimPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix)
689+
if localhostProfile != "" {
690+
return &api.SeccompProfile{
691+
Type: api.SeccompProfileTypeLocalhost,
692+
LocalhostProfile: &localhostProfile,
693+
}
694+
}
695+
}
696+
697+
// we can only reach this code path if the localhostProfile name has a zero
698+
// length or if the annotation has an unrecognized value
699+
return nil
700+
}

0 commit comments

Comments
 (0)