Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add seccomp GA version skew for pods #91408

Merged
merged 1 commit into from Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkg/registry/core/pod/BUILD
Expand Up @@ -22,6 +22,7 @@ go_library(
"//pkg/features:go_default_library",
"//pkg/kubelet/client:go_default_library",
"//pkg/proxy/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
Expand Down Expand Up @@ -49,6 +50,7 @@ go_test(
"//pkg/apis/core/install:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubelet/client:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
Expand All @@ -60,6 +62,7 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
],
)

Expand Down
129 changes: 129 additions & 0 deletions pkg/registry/core/pod/strategy.go
Expand Up @@ -26,6 +26,7 @@ import (
"strings"
"time"

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

podutil.DropDisabledPodFields(pod, nil)

applySeccompVersionSkew(pod)
}

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

return container, nil
}

// applySeccompVersionSkew implements the version skew behavior described in:
// https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/20190717-seccomp-ga.md#version-skew-strategy
func applySeccompVersionSkew(pod *api.Pod) {
// get possible annotation and field
annotation, hasAnnotation := pod.Annotations[v1.SeccompPodAnnotationKey]
field, hasField := (*api.SeccompProfile)(nil), false

if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil {
field = pod.Spec.SecurityContext.SeccompProfile
hasField = true
}

// sync field and annotation
if hasField && !hasAnnotation {
newAnnotation := seccompAnnotationForField(field)

if newAnnotation != "" {
if pod.Annotations == nil {
pod.Annotations = map[string]string{}
}
pod.Annotations[v1.SeccompPodAnnotationKey] = newAnnotation
}
} else if hasAnnotation && !hasField {
newField := seccompFieldForAnnotation(annotation)

if newField != nil {
if pod.Spec.SecurityContext == nil {
pod.Spec.SecurityContext = &api.PodSecurityContext{}
}
pod.Spec.SecurityContext.SeccompProfile = newField
}
}

// 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.SeccompContainerAnnotationKeyPrefix + ctr.Name
annotation, hasAnnotation := pod.Annotations[key]

field, hasField := (*api.SeccompProfile)(nil), false
if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil {
field = ctr.SecurityContext.SeccompProfile
hasField = true
}

// sync field and annotation
if hasField && !hasAnnotation {
newAnnotation := seccompAnnotationForField(field)

if newAnnotation != "" {
if pod.Annotations == nil {
pod.Annotations = map[string]string{}
}
pod.Annotations[key] = newAnnotation
}
} else if hasAnnotation && !hasField {
newField := seccompFieldForAnnotation(annotation)

if newField != nil {
if ctr.SecurityContext == nil {
ctr.SecurityContext = &api.SecurityContext{}
}
ctr.SecurityContext.SeccompProfile = newField
}
}

return true
})
}

// seccompFieldForAnnotation takes a pod seccomp profile field and returns the
// converted annotation value
func seccompAnnotationForField(field *api.SeccompProfile) string {
// If only seccomp 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.SeccompProfileTypeUnconfined:
return v1.SeccompProfileNameUnconfined

case api.SeccompProfileTypeRuntimeDefault:
return v1.SeccompProfileRuntimeDefault

case api.SeccompProfileTypeLocalhost:
if field.LocalhostProfile != nil {
return v1.SeccompLocalhostProfileNamePrefix + *field.LocalhostProfile
}
}

// we can only reach this code path if the LocalhostProfile is nil but the
// provided field type is SeccompProfileTypeLocalhost or if an unrecognized
// type is specified
return ""
}

// seccompFieldForAnnotation takes a pod annotation and returns the converted
// seccomp profile field.
func seccompFieldForAnnotation(annotation string) *api.SeccompProfile {
// If only seccomp annotations are specified, copy the values into the
// corresponding fields. This ensures that existing applications continue
// to enforce seccomp, and prevents the kubelet from needing to resolve
// annotations & fields.
if annotation == v1.SeccompProfileNameUnconfined {
return &api.SeccompProfile{Type: api.SeccompProfileTypeUnconfined}
}

if annotation == api.SeccompProfileRuntimeDefault || annotation == api.DeprecatedSeccompProfileDockerDefault {
return &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault}
}

if strings.HasPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix) {
localhostProfile := strings.TrimPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix)
if localhostProfile != "" {
return &api.SeccompProfile{
Type: api.SeccompProfileTypeLocalhost,
LocalhostProfile: &localhostProfile,
saschagrunert marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

// 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
}