mirror of https://github.com/k3s-io/k3s
Merge pull request #28300 from pweil-/psp-seccomp
Automatic merge from submit-queue Add PSP support for seccomp profiles Seccomp support for PSP. There are still a couple of TODOs that need to be fixed but this is passing tests. One thing of note, since seccomp is all being stored in annotations right now it breaks some of the assumptions we've stated for the provider in terms of mutating the passed in pod. I've put big warning comments around the pieces that do that to make sure it's clear and covered the rollback in admission if the policy fails to validate. @sttts @pmorie @erictune @smarterclayton @liggittpull/6/head
commit
c592a46e16
|
@ -2119,7 +2119,7 @@ func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateSeccompProfile(p string, fldPath *field.Path) field.ErrorList {
|
||||
func ValidateSeccompProfile(p string, fldPath *field.Path) field.ErrorList {
|
||||
if p == "docker/default" {
|
||||
return nil
|
||||
}
|
||||
|
@ -2135,11 +2135,11 @@ func validateSeccompProfile(p string, fldPath *field.Path) field.ErrorList {
|
|||
func ValidateSeccompPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if p, exists := annotations[api.SeccompPodAnnotationKey]; exists {
|
||||
allErrs = append(allErrs, validateSeccompProfile(p, fldPath.Child(api.SeccompPodAnnotationKey))...)
|
||||
allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(api.SeccompPodAnnotationKey))...)
|
||||
}
|
||||
for k, p := range annotations {
|
||||
if strings.HasPrefix(k, api.SeccompContainerAnnotationKeyPrefix) {
|
||||
allErrs = append(allErrs, validateSeccompProfile(p, fldPath.Child(k))...)
|
||||
allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(k))...)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
|
@ -600,6 +601,14 @@ func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string,
|
|||
allErrs = append(allErrs, validatePodSecurityPolicySysctls(sysctlFldPath, sysctls)...)
|
||||
}
|
||||
|
||||
if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" {
|
||||
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...)
|
||||
}
|
||||
if allowed := annotations[seccomp.AllowedProfilesAnnotationKey]; allowed != "" {
|
||||
for _, p := range strings.Split(allowed, ",") {
|
||||
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.AllowedProfilesAnnotationKey))...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||
|
@ -1604,6 +1605,15 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
invalidSysctlPattern := validPSP()
|
||||
invalidSysctlPattern.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "a.*.b"
|
||||
|
||||
invalidSeccompDefault := validPSP()
|
||||
invalidSeccompDefault.Annotations = map[string]string{
|
||||
seccomp.DefaultProfileAnnotationKey: "not-good",
|
||||
}
|
||||
invalidSeccompAllowed := validPSP()
|
||||
invalidSeccompAllowed.Annotations = map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: "docker/default,not-good",
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
psp *extensions.PodSecurityPolicy
|
||||
errorType field.ErrorType
|
||||
|
@ -1700,6 +1710,16 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
|
||||
},
|
||||
"invalid seccomp default profile": {
|
||||
psp: invalidSeccompDefault,
|
||||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: "must be a valid seccomp profile",
|
||||
},
|
||||
"invalid seccomp allowed profile": {
|
||||
psp: invalidSeccompAllowed,
|
||||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: "must be a valid seccomp profile",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
|
@ -1768,6 +1788,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
withSysctl := validPSP()
|
||||
withSysctl.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "net.*"
|
||||
|
||||
validSeccomp := validPSP()
|
||||
validSeccomp.Annotations = map[string]string{
|
||||
seccomp.DefaultProfileAnnotationKey: "docker/default",
|
||||
seccomp.AllowedProfilesAnnotationKey: "docker/default,unconfined,localhost/foo",
|
||||
}
|
||||
|
||||
successCases := map[string]struct {
|
||||
psp *extensions.PodSecurityPolicy
|
||||
}{
|
||||
|
@ -1792,6 +1818,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
"with network sysctls": {
|
||||
psp: withSysctl,
|
||||
},
|
||||
"valid seccomp annotations": {
|
||||
psp: validSeccomp,
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range successCases {
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
|
||||
|
@ -56,6 +57,11 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
|
|||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
seccompStrat, err := createSeccompStrategy(psp)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
@ -92,6 +98,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
|
|||
FSGroupStrategy: fsGroupStrat,
|
||||
SupplementalGroupStrategy: supGroupStrat,
|
||||
CapabilitiesStrategy: capStrat,
|
||||
SeccompStrategy: seccompStrat,
|
||||
SysctlsStrategy: sysctlsStrat,
|
||||
}
|
||||
|
||||
|
@ -129,6 +136,11 @@ func createAppArmorStrategy(psp *extensions.PodSecurityPolicy) (apparmor.Strateg
|
|||
return apparmor.NewStrategy(psp.Annotations), nil
|
||||
}
|
||||
|
||||
// createSeccompStrategy creates a new seccomp strategy.
|
||||
func createSeccompStrategy(psp *extensions.PodSecurityPolicy) (seccomp.Strategy, error) {
|
||||
return seccomp.NewStrategy(psp.Annotations), nil
|
||||
}
|
||||
|
||||
// createFSGroupStrategy creates a new fsgroup strategy
|
||||
func createFSGroupStrategy(opts *extensions.FSGroupStrategyOptions) (group.GroupStrategy, error) {
|
||||
switch opts.Rule {
|
||||
|
|
|
@ -103,6 +103,18 @@ func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurit
|
|||
sc.SELinuxOptions = seLinux
|
||||
}
|
||||
|
||||
// This is only generated on the pod level. Containers inherit the pod's profile. If the
|
||||
// container has a specific profile set then it will be caught in the validation step.
|
||||
seccompProfile, err := s.strategies.SeccompStrategy.Generate(annotations, pod)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if seccompProfile != "" {
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[api.SeccompPodAnnotationKey] = seccompProfile
|
||||
}
|
||||
return sc, annotations, nil
|
||||
}
|
||||
|
||||
|
@ -188,6 +200,7 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
|
|||
}
|
||||
allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(pod, fsGroups)...)
|
||||
allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(pod, pod.Spec.SecurityContext.SupplementalGroups)...)
|
||||
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...)
|
||||
|
||||
// make a dummy container context to reuse the selinux strategies
|
||||
container := &api.Container{
|
||||
|
@ -247,6 +260,7 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe
|
|||
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...)
|
||||
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
|
||||
allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...)
|
||||
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...)
|
||||
|
||||
if !s.psp.Spec.Privileged && *sc.Privileged {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed"))
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
|
||||
"k8s.io/kubernetes/pkg/util/diff"
|
||||
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||
|
@ -49,6 +50,10 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
|
|||
return &extensions.PodSecurityPolicy{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "psp-sa",
|
||||
Annotations: map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: "*",
|
||||
seccomp.DefaultProfileAnnotationKey: "foo",
|
||||
},
|
||||
},
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
DefaultAddCapabilities: []api.Capability{"foo"},
|
||||
|
@ -121,6 +126,10 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
|
|||
return &extensions.PodSecurityPolicy{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "psp-sa",
|
||||
Annotations: map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: "*",
|
||||
seccomp.DefaultProfileAnnotationKey: "foo",
|
||||
},
|
||||
},
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
DefaultAddCapabilities: []api.Capability{"foo"},
|
||||
|
@ -238,6 +247,9 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||
failUnsafeSysctlFooPod := defaultPod()
|
||||
failUnsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
|
||||
|
||||
failSeccompProfilePod := defaultPod()
|
||||
failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"}
|
||||
|
||||
errorCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
psp *extensions.PodSecurityPolicy
|
||||
|
@ -313,6 +325,11 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||
psp: failOtherSysctlsAllowedPSP,
|
||||
expectedError: "sysctl \"foo\" is not allowed",
|
||||
},
|
||||
"failInvalidSeccomp": {
|
||||
pod: failSeccompProfilePod,
|
||||
psp: defaultPSP(),
|
||||
expectedError: "Forbidden: seccomp may not be set",
|
||||
},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory())
|
||||
|
@ -382,6 +399,16 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
|
|||
readOnlyRootFS := false
|
||||
readOnlyRootFSPodFalse.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFS
|
||||
|
||||
failSeccompPod := defaultPod()
|
||||
failSeccompPod.Annotations = map[string]string{
|
||||
api.SeccompContainerAnnotationKeyPrefix + failSeccompPod.Spec.Containers[0].Name: "foo",
|
||||
}
|
||||
|
||||
failSeccompPodInheritPodAnnotation := defaultPod()
|
||||
failSeccompPodInheritPodAnnotation.Annotations = map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
}
|
||||
|
||||
errorCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
psp *extensions.PodSecurityPolicy
|
||||
|
@ -432,6 +459,16 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
|
|||
psp: readOnlyRootFSPSP,
|
||||
expectedError: "ReadOnlyRootFilesystem must be set to true",
|
||||
},
|
||||
"failSeccompContainerAnnotation": {
|
||||
pod: failSeccompPod,
|
||||
psp: defaultPSP(),
|
||||
expectedError: "Forbidden: seccomp may not be set",
|
||||
},
|
||||
"failSeccompContainerPodAnnotation": {
|
||||
pod: failSeccompPodInheritPodAnnotation,
|
||||
psp: defaultPSP(),
|
||||
expectedError: "Forbidden: seccomp may not be set",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
|
@ -512,6 +549,16 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
|
|||
unsafeSysctlFooPod := defaultPod()
|
||||
unsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
|
||||
|
||||
seccompPSP := defaultPSP()
|
||||
seccompPSP.Annotations = map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: "foo",
|
||||
}
|
||||
|
||||
seccompPod := defaultPod()
|
||||
seccompPod.Annotations = map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
}
|
||||
|
||||
errorCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
psp *extensions.PodSecurityPolicy
|
||||
|
@ -556,6 +603,10 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
|
|||
pod: unsafeSysctlFooPod,
|
||||
psp: defaultPSP(),
|
||||
},
|
||||
"pass seccomp validating PSP": {
|
||||
pod: seccompPod,
|
||||
psp: seccompPSP,
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
|
@ -667,6 +718,21 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
|
|||
readOnlyRootFSTrue := true
|
||||
readOnlyRootFSPodTrue.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFSTrue
|
||||
|
||||
seccompPSP := defaultPSP()
|
||||
seccompPSP.Annotations = map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: "foo",
|
||||
}
|
||||
|
||||
seccompPod := defaultPod()
|
||||
seccompPod.Annotations = map[string]string{
|
||||
api.SeccompContainerAnnotationKeyPrefix + seccompPod.Spec.Containers[0].Name: "foo",
|
||||
}
|
||||
|
||||
seccompPodInherit := defaultPod()
|
||||
seccompPodInherit.Annotations = map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
}
|
||||
|
||||
errorCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
psp *extensions.PodSecurityPolicy
|
||||
|
@ -715,6 +781,14 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
|
|||
pod: readOnlyRootFSPodTrue,
|
||||
psp: defaultPSP(),
|
||||
},
|
||||
"pass seccomp container annotation": {
|
||||
pod: seccompPod,
|
||||
psp: seccompPSP,
|
||||
},
|
||||
"pass seccomp inherit pod annotation": {
|
||||
pod: seccompPodInherit,
|
||||
psp: seccompPSP,
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
const (
|
||||
// AllowAny is the wildcard used to allow any profile.
|
||||
AllowAny = "*"
|
||||
// The annotation key specifying the default seccomp profile.
|
||||
DefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName"
|
||||
// The annotation key specifying the allowed seccomp profiles.
|
||||
AllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames"
|
||||
)
|
||||
|
||||
// Strategy defines the interface for all seccomp constraint strategies.
|
||||
type Strategy interface {
|
||||
// Generate returns a profile based on constraint rules.
|
||||
Generate(annotations map[string]string, pod *api.Pod) (string, error)
|
||||
// Validate ensures that the specified values fall within the range of the strategy.
|
||||
ValidatePod(pod *api.Pod) field.ErrorList
|
||||
// Validate ensures that the specified values fall within the range of the strategy.
|
||||
ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList
|
||||
}
|
||||
|
||||
type strategy struct {
|
||||
defaultProfile string
|
||||
allowedProfiles map[string]bool
|
||||
// For printing error messages (preserves order).
|
||||
allowedProfilesString string
|
||||
// does the strategy allow any profile (wildcard)
|
||||
allowAnyProfile bool
|
||||
}
|
||||
|
||||
var _ Strategy = &strategy{}
|
||||
|
||||
// NewStrategy creates a new strategy that enforces seccomp profile constraints.
|
||||
func NewStrategy(pspAnnotations map[string]string) Strategy {
|
||||
var allowedProfiles map[string]bool
|
||||
allowAnyProfile := false
|
||||
if allowed, ok := pspAnnotations[AllowedProfilesAnnotationKey]; ok {
|
||||
profiles := strings.Split(allowed, ",")
|
||||
allowedProfiles = make(map[string]bool, len(profiles))
|
||||
for _, p := range profiles {
|
||||
if p == AllowAny {
|
||||
allowAnyProfile = true
|
||||
continue
|
||||
}
|
||||
allowedProfiles[p] = true
|
||||
}
|
||||
}
|
||||
return &strategy{
|
||||
defaultProfile: pspAnnotations[DefaultProfileAnnotationKey],
|
||||
allowedProfiles: allowedProfiles,
|
||||
allowedProfilesString: pspAnnotations[AllowedProfilesAnnotationKey],
|
||||
allowAnyProfile: allowAnyProfile,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate returns a profile based on constraint rules.
|
||||
func (s *strategy) Generate(annotations map[string]string, pod *api.Pod) (string, error) {
|
||||
if annotations[api.SeccompPodAnnotationKey] != "" {
|
||||
// Profile already set, nothing to do.
|
||||
return annotations[api.SeccompPodAnnotationKey], nil
|
||||
}
|
||||
return s.defaultProfile, nil
|
||||
}
|
||||
|
||||
// ValidatePod ensures that the specified values on the pod fall within the range
|
||||
// of the strategy.
|
||||
func (s *strategy) ValidatePod(pod *api.Pod) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
podSpecFieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompPodAnnotationKey)
|
||||
podProfile := pod.Annotations[api.SeccompPodAnnotationKey]
|
||||
|
||||
if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && podProfile != "" {
|
||||
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, "seccomp may not be set"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if !s.profileAllowed(podProfile) {
|
||||
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", podProfile, s.allowedProfilesString)
|
||||
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, msg))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateContainer ensures that the specified values on the container fall within
|
||||
// the range of the strategy.
|
||||
func (s *strategy) ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompContainerAnnotationKeyPrefix + container.Name)
|
||||
containerProfile := profileForContainer(pod, container)
|
||||
|
||||
if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && containerProfile != "" {
|
||||
allErrs = append(allErrs, field.Forbidden(fieldPath, "seccomp may not be set"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if !s.profileAllowed(containerProfile) {
|
||||
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", containerProfile, s.allowedProfilesString)
|
||||
allErrs = append(allErrs, field.Forbidden(fieldPath, msg))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// profileAllowed checks if profile is in allowedProfiles or if allowedProfiles
|
||||
// contains the wildcard.
|
||||
func (s *strategy) profileAllowed(profile string) bool {
|
||||
// for backwards compatibility and PSPs without a defined list of allowed profiles.
|
||||
// If a PSP does not have allowedProfiles set then we should allow an empty profile.
|
||||
// This will mean that the runtime default is used.
|
||||
if len(s.allowedProfiles) == 0 && profile == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
return s.allowAnyProfile || s.allowedProfiles[profile]
|
||||
}
|
||||
|
||||
// profileForContainer returns the container profile if set, otherwise the pod profile.
|
||||
func profileForContainer(pod *api.Pod, container *api.Container) string {
|
||||
containerProfile, ok := pod.Annotations[api.SeccompContainerAnnotationKeyPrefix+container.Name]
|
||||
if ok {
|
||||
return containerProfile
|
||||
}
|
||||
return pod.Annotations[api.SeccompPodAnnotationKey]
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
var (
|
||||
withoutSeccomp = map[string]string{"foo": "bar"}
|
||||
allowAnyNoDefault = map[string]string{
|
||||
AllowedProfilesAnnotationKey: "*",
|
||||
}
|
||||
allowAnyDefault = map[string]string{
|
||||
AllowedProfilesAnnotationKey: "*",
|
||||
DefaultProfileAnnotationKey: "foo",
|
||||
}
|
||||
allowAnyAndSpecificDefault = map[string]string{
|
||||
AllowedProfilesAnnotationKey: "*,bar",
|
||||
DefaultProfileAnnotationKey: "foo",
|
||||
}
|
||||
allowSpecific = map[string]string{
|
||||
AllowedProfilesAnnotationKey: "foo",
|
||||
}
|
||||
)
|
||||
|
||||
func TestNewStrategy(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
annotations map[string]string
|
||||
expectedAllowedProfilesString string
|
||||
expectedAllowAny bool
|
||||
expectedAllowedProfiles map[string]bool
|
||||
expectedDefaultProfile string
|
||||
}{
|
||||
"no seccomp": {
|
||||
annotations: withoutSeccomp,
|
||||
expectedAllowAny: false,
|
||||
expectedAllowedProfilesString: "",
|
||||
expectedAllowedProfiles: nil,
|
||||
expectedDefaultProfile: "",
|
||||
},
|
||||
"allow any, no default": {
|
||||
annotations: allowAnyNoDefault,
|
||||
expectedAllowAny: true,
|
||||
expectedAllowedProfilesString: "*",
|
||||
expectedAllowedProfiles: map[string]bool{},
|
||||
expectedDefaultProfile: "",
|
||||
},
|
||||
"allow any, default": {
|
||||
annotations: allowAnyDefault,
|
||||
expectedAllowAny: true,
|
||||
expectedAllowedProfilesString: "*",
|
||||
expectedAllowedProfiles: map[string]bool{},
|
||||
expectedDefaultProfile: "foo",
|
||||
},
|
||||
"allow any and specific, default": {
|
||||
annotations: allowAnyAndSpecificDefault,
|
||||
expectedAllowAny: true,
|
||||
expectedAllowedProfilesString: "*,bar",
|
||||
expectedAllowedProfiles: map[string]bool{
|
||||
"bar": true,
|
||||
},
|
||||
expectedDefaultProfile: "foo",
|
||||
},
|
||||
}
|
||||
for k, v := range tests {
|
||||
strat := NewStrategy(v.annotations)
|
||||
internalStrat, _ := strat.(*strategy)
|
||||
|
||||
if internalStrat.allowAnyProfile != v.expectedAllowAny {
|
||||
t.Errorf("%s expected allowAnyProfile to be %t but found %t", k, v.expectedAllowAny, internalStrat.allowAnyProfile)
|
||||
}
|
||||
if internalStrat.allowedProfilesString != v.expectedAllowedProfilesString {
|
||||
t.Errorf("%s expected allowedProfilesString to be %s but found %s", k, v.expectedAllowedProfilesString, internalStrat.allowedProfilesString)
|
||||
}
|
||||
if internalStrat.defaultProfile != v.expectedDefaultProfile {
|
||||
t.Errorf("%s expected defaultProfile to be %s but found %s", k, v.expectedDefaultProfile, internalStrat.defaultProfile)
|
||||
}
|
||||
if !reflect.DeepEqual(v.expectedAllowedProfiles, internalStrat.allowedProfiles) {
|
||||
t.Errorf("%s expected expectedAllowedProfiles to be %#v but found %#v", k, v.expectedAllowedProfiles, internalStrat.allowedProfiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
pspAnnotations map[string]string
|
||||
podAnnotations map[string]string
|
||||
expectedProfile string
|
||||
}{
|
||||
"no seccomp, no pod annotations": {
|
||||
pspAnnotations: withoutSeccomp,
|
||||
podAnnotations: nil,
|
||||
expectedProfile: "",
|
||||
},
|
||||
"no seccomp, pod annotations": {
|
||||
pspAnnotations: withoutSeccomp,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
expectedProfile: "foo",
|
||||
},
|
||||
"seccomp with no default, no pod annotations": {
|
||||
pspAnnotations: allowAnyNoDefault,
|
||||
podAnnotations: nil,
|
||||
expectedProfile: "",
|
||||
},
|
||||
"seccomp with no default, pod annotations": {
|
||||
pspAnnotations: allowAnyNoDefault,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
expectedProfile: "foo",
|
||||
},
|
||||
"seccomp with default, no pod annotations": {
|
||||
pspAnnotations: allowAnyDefault,
|
||||
podAnnotations: nil,
|
||||
expectedProfile: "foo",
|
||||
},
|
||||
"seccomp with default, pod annotations": {
|
||||
pspAnnotations: allowAnyDefault,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "bar",
|
||||
},
|
||||
expectedProfile: "bar",
|
||||
},
|
||||
}
|
||||
for k, v := range tests {
|
||||
strat := NewStrategy(v.pspAnnotations)
|
||||
actual, err := strat.Generate(v.podAnnotations, nil)
|
||||
if err != nil {
|
||||
t.Errorf("%s received error during generation %#v", k, err)
|
||||
continue
|
||||
}
|
||||
if actual != v.expectedProfile {
|
||||
t.Errorf("%s expected profile %s but received %s", k, v.expectedProfile, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePod(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
pspAnnotations map[string]string
|
||||
podAnnotations map[string]string
|
||||
expectedError string
|
||||
}{
|
||||
"no pod annotations, required profiles": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: nil,
|
||||
expectedError: "Forbidden: is not an allowed seccomp profile. Valid values are foo",
|
||||
},
|
||||
"no pod annotations, no required profiles": {
|
||||
pspAnnotations: withoutSeccomp,
|
||||
podAnnotations: nil,
|
||||
expectedError: "",
|
||||
},
|
||||
"valid pod annotations, required profiles": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
"invalid pod annotations, required profiles": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "bar",
|
||||
},
|
||||
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
|
||||
},
|
||||
"pod annotations, no required profiles": {
|
||||
pspAnnotations: withoutSeccomp,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
expectedError: "Forbidden: seccomp may not be set",
|
||||
},
|
||||
"pod annotations, allow any": {
|
||||
pspAnnotations: allowAnyNoDefault,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
"no pod annotations, allow any": {
|
||||
pspAnnotations: allowAnyNoDefault,
|
||||
podAnnotations: nil,
|
||||
expectedError: "",
|
||||
},
|
||||
}
|
||||
for k, v := range tests {
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Annotations: v.podAnnotations,
|
||||
},
|
||||
}
|
||||
strat := NewStrategy(v.pspAnnotations)
|
||||
errs := strat.ValidatePod(pod)
|
||||
if v.expectedError == "" && len(errs) != 0 {
|
||||
t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error())
|
||||
}
|
||||
if v.expectedError != "" && len(errs) == 0 {
|
||||
t.Errorf("%s expected error %s but received none", k, v.expectedError)
|
||||
}
|
||||
if v.expectedError != "" && len(errs) > 1 {
|
||||
t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error())
|
||||
}
|
||||
if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) {
|
||||
t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateContainer(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
pspAnnotations map[string]string
|
||||
podAnnotations map[string]string
|
||||
expectedError string
|
||||
}{
|
||||
"no pod annotations, required profiles": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: nil,
|
||||
expectedError: "Forbidden: is not an allowed seccomp profile. Valid values are foo",
|
||||
},
|
||||
"no pod annotations, no required profiles": {
|
||||
pspAnnotations: withoutSeccomp,
|
||||
podAnnotations: nil,
|
||||
expectedError: "",
|
||||
},
|
||||
"valid pod annotations, required profiles": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
"invalid pod annotations, required profiles": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompContainerAnnotationKeyPrefix + "container": "bar",
|
||||
},
|
||||
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
|
||||
},
|
||||
"pod annotations, no required profiles": {
|
||||
pspAnnotations: withoutSeccomp,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
|
||||
},
|
||||
expectedError: "Forbidden: seccomp may not be set",
|
||||
},
|
||||
"pod annotations, allow any": {
|
||||
pspAnnotations: allowAnyNoDefault,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
"no pod annotations, allow any": {
|
||||
pspAnnotations: allowAnyNoDefault,
|
||||
podAnnotations: nil,
|
||||
expectedError: "",
|
||||
},
|
||||
"container inherits valid pod annotation": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
"container inherits invalid pod annotation": {
|
||||
pspAnnotations: allowSpecific,
|
||||
podAnnotations: map[string]string{
|
||||
api.SeccompPodAnnotationKey: "bar",
|
||||
},
|
||||
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
|
||||
},
|
||||
}
|
||||
for k, v := range tests {
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Annotations: v.podAnnotations,
|
||||
},
|
||||
}
|
||||
container := &api.Container{
|
||||
Name: "container",
|
||||
}
|
||||
|
||||
strat := NewStrategy(v.pspAnnotations)
|
||||
errs := strat.ValidateContainer(pod, container)
|
||||
if v.expectedError == "" && len(errs) != 0 {
|
||||
t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error())
|
||||
}
|
||||
if v.expectedError != "" && len(errs) == 0 {
|
||||
t.Errorf("%s expected error %s but received none", k, v.expectedError)
|
||||
}
|
||||
if v.expectedError != "" && len(errs) > 1 {
|
||||
t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error())
|
||||
}
|
||||
if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) {
|
||||
t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
|
||||
|
@ -65,4 +66,5 @@ type ProviderStrategies struct {
|
|||
SupplementalGroupStrategy group.GroupStrategy
|
||||
CapabilitiesStrategy capabilities.Strategy
|
||||
SysctlsStrategy sysctl.SysctlsStrategy
|
||||
SeccompStrategy seccomp.Strategy
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
|
||||
"k8s.io/kubernetes/pkg/util/diff"
|
||||
)
|
||||
|
@ -55,6 +56,115 @@ func useInitContainers(pod *kapi.Pod) *kapi.Pod {
|
|||
return pod
|
||||
}
|
||||
|
||||
func TestAdmitSeccomp(t *testing.T) {
|
||||
containerName := "container"
|
||||
tests := map[string]struct {
|
||||
pspAnnotations map[string]string
|
||||
podAnnotations map[string]string
|
||||
shouldAdmit bool
|
||||
}{
|
||||
"no seccomp, no pod annotations": {
|
||||
pspAnnotations: nil,
|
||||
podAnnotations: nil,
|
||||
shouldAdmit: true,
|
||||
},
|
||||
"no seccomp, pod annotations": {
|
||||
pspAnnotations: nil,
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
shouldAdmit: false,
|
||||
},
|
||||
"no seccomp, container annotations": {
|
||||
pspAnnotations: nil,
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo",
|
||||
},
|
||||
shouldAdmit: false,
|
||||
},
|
||||
"seccomp, allow any no pod annotation": {
|
||||
pspAnnotations: map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny,
|
||||
},
|
||||
podAnnotations: nil,
|
||||
shouldAdmit: true,
|
||||
},
|
||||
"seccomp, allow any pod annotation": {
|
||||
pspAnnotations: map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny,
|
||||
},
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
shouldAdmit: true,
|
||||
},
|
||||
"seccomp, allow any container annotation": {
|
||||
pspAnnotations: map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny,
|
||||
},
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo",
|
||||
},
|
||||
shouldAdmit: true,
|
||||
},
|
||||
"seccomp, allow specific pod annotation failure": {
|
||||
pspAnnotations: map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: "foo",
|
||||
},
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompPodAnnotationKey: "bar",
|
||||
},
|
||||
shouldAdmit: false,
|
||||
},
|
||||
"seccomp, allow specific container annotation failure": {
|
||||
pspAnnotations: map[string]string{
|
||||
// provide a default so we don't have to give the pod annotation
|
||||
seccomp.DefaultProfileAnnotationKey: "foo",
|
||||
seccomp.AllowedProfilesAnnotationKey: "foo",
|
||||
},
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "bar",
|
||||
},
|
||||
shouldAdmit: false,
|
||||
},
|
||||
"seccomp, allow specific pod annotation pass": {
|
||||
pspAnnotations: map[string]string{
|
||||
seccomp.AllowedProfilesAnnotationKey: "foo",
|
||||
},
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompPodAnnotationKey: "foo",
|
||||
},
|
||||
shouldAdmit: true,
|
||||
},
|
||||
"seccomp, allow specific container annotation pass": {
|
||||
pspAnnotations: map[string]string{
|
||||
// provide a default so we don't have to give the pod annotation
|
||||
seccomp.DefaultProfileAnnotationKey: "foo",
|
||||
seccomp.AllowedProfilesAnnotationKey: "foo,bar",
|
||||
},
|
||||
podAnnotations: map[string]string{
|
||||
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "bar",
|
||||
},
|
||||
shouldAdmit: true,
|
||||
},
|
||||
}
|
||||
for k, v := range tests {
|
||||
psp := restrictivePSP()
|
||||
psp.Annotations = v.pspAnnotations
|
||||
pod := &kapi.Pod{
|
||||
ObjectMeta: kapi.ObjectMeta{
|
||||
Annotations: v.podAnnotations,
|
||||
},
|
||||
Spec: kapi.PodSpec{
|
||||
Containers: []kapi.Container{
|
||||
{Name: containerName},
|
||||
},
|
||||
},
|
||||
}
|
||||
testPSPAdmit(k, []*extensions.PodSecurityPolicy{psp}, pod, v.shouldAdmit, psp.Name, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitPrivileged(t *testing.T) {
|
||||
createPodWithPriv := func(priv bool) *kapi.Pod {
|
||||
pod := goodPod()
|
||||
|
|
Loading…
Reference in New Issue