From 410ac59c0da01eca74d593f1a38419e77369a7fe Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 4 May 2022 15:27:06 -0400 Subject: [PATCH] Remove PodSecurityPolicy admission plugin --- pkg/apis/policy/validation/validation.go | 61 +- pkg/apis/policy/validation/validation_test.go | 14 +- pkg/kubeapiserver/options/plugins.go | 5 +- pkg/kubelet/kubelet.go | 3 +- pkg/kubelet/sysctl/allowlist_test.go | 6 +- .../sysctl/safe_sysctls.go} | 22 +- pkg/security/podsecuritypolicy/OWNERS | 8 - .../podsecuritypolicy/apparmor/strategy.go | 111 - .../apparmor/strategy_test.go | 174 -- .../capabilities/capabilities.go | 165 -- .../capabilities/capabilities_test.go | 412 --- .../podsecuritypolicy/capabilities/doc.go | 19 - .../podsecuritypolicy/capabilities/types.go | 30 - pkg/security/podsecuritypolicy/doc.go | 20 - pkg/security/podsecuritypolicy/factory.go | 196 -- pkg/security/podsecuritypolicy/group/doc.go | 19 - .../podsecuritypolicy/group/helpers.go | 46 - .../podsecuritypolicy/group/mayrunas.go | 59 - .../podsecuritypolicy/group/mayrunas_test.go | 185 -- .../podsecuritypolicy/group/mustrunas.go | 71 - .../podsecuritypolicy/group/mustrunas_test.go | 180 -- .../podsecuritypolicy/group/runasany.go | 49 - .../podsecuritypolicy/group/runasany_test.go | 62 - pkg/security/podsecuritypolicy/group/types.go | 35 - pkg/security/podsecuritypolicy/provider.go | 459 --- .../podsecuritypolicy/provider_test.go | 1727 ------------ .../podsecuritypolicy/seccomp/strategy.go | 178 -- .../seccomp/strategy_test.go | 419 --- pkg/security/podsecuritypolicy/selinux/doc.go | 19 - .../podsecuritypolicy/selinux/mustrunas.go | 126 - .../selinux/mustrunas_test.go | 183 -- .../podsecuritypolicy/selinux/runasany.go | 43 - .../selinux/runasany_test.go | 72 - .../podsecuritypolicy/selinux/types.go | 30 - .../sysctl/mustmatchpatterns.go | 126 - .../sysctl/mustmatchpatterns_test.go | 104 - pkg/security/podsecuritypolicy/types.go | 67 - pkg/security/podsecuritypolicy/user/doc.go | 19 - .../podsecuritypolicy/user/mustrunas.go | 74 - .../podsecuritypolicy/user/mustrunas_test.go | 144 - .../podsecuritypolicy/user/nonroot.go | 59 - .../podsecuritypolicy/user/nonroot_test.go | 119 - .../podsecuritypolicy/user/runasany.go | 43 - .../podsecuritypolicy/user/runasany_test.go | 59 - pkg/security/podsecuritypolicy/user/types.go | 31 - pkg/security/podsecuritypolicy/util/doc.go | 19 - pkg/security/podsecuritypolicy/util/util.go | 276 -- .../podsecuritypolicy/util/util_test.go | 471 ---- .../security/podsecuritypolicy/OWNERS | 8 - .../security/podsecuritypolicy/admission.go | 380 --- .../podsecuritypolicy/admission_test.go | 2451 ----------------- test/e2e/auth/pod_security_policy.go | 367 --- test/e2e/framework/framework.go | 5 - test/e2e/framework/psp.go | 192 -- test/e2e/storage/utils/utils.go | 55 - 55 files changed, 76 insertions(+), 10201 deletions(-) rename pkg/{security/podsecuritypolicy/sysctl/types.go => kubelet/sysctl/safe_sysctls.go} (54%) delete mode 100644 pkg/security/podsecuritypolicy/OWNERS delete mode 100644 pkg/security/podsecuritypolicy/apparmor/strategy.go delete mode 100644 pkg/security/podsecuritypolicy/apparmor/strategy_test.go delete mode 100644 pkg/security/podsecuritypolicy/capabilities/capabilities.go delete mode 100644 pkg/security/podsecuritypolicy/capabilities/capabilities_test.go delete mode 100644 pkg/security/podsecuritypolicy/capabilities/doc.go delete mode 100644 pkg/security/podsecuritypolicy/capabilities/types.go delete mode 100644 pkg/security/podsecuritypolicy/doc.go delete mode 100644 pkg/security/podsecuritypolicy/factory.go delete mode 100644 pkg/security/podsecuritypolicy/group/doc.go delete mode 100644 pkg/security/podsecuritypolicy/group/helpers.go delete mode 100644 pkg/security/podsecuritypolicy/group/mayrunas.go delete mode 100644 pkg/security/podsecuritypolicy/group/mayrunas_test.go delete mode 100644 pkg/security/podsecuritypolicy/group/mustrunas.go delete mode 100644 pkg/security/podsecuritypolicy/group/mustrunas_test.go delete mode 100644 pkg/security/podsecuritypolicy/group/runasany.go delete mode 100644 pkg/security/podsecuritypolicy/group/runasany_test.go delete mode 100644 pkg/security/podsecuritypolicy/group/types.go delete mode 100644 pkg/security/podsecuritypolicy/provider.go delete mode 100644 pkg/security/podsecuritypolicy/provider_test.go delete mode 100644 pkg/security/podsecuritypolicy/seccomp/strategy.go delete mode 100644 pkg/security/podsecuritypolicy/seccomp/strategy_test.go delete mode 100644 pkg/security/podsecuritypolicy/selinux/doc.go delete mode 100644 pkg/security/podsecuritypolicy/selinux/mustrunas.go delete mode 100644 pkg/security/podsecuritypolicy/selinux/mustrunas_test.go delete mode 100644 pkg/security/podsecuritypolicy/selinux/runasany.go delete mode 100644 pkg/security/podsecuritypolicy/selinux/runasany_test.go delete mode 100644 pkg/security/podsecuritypolicy/selinux/types.go delete mode 100644 pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go delete mode 100644 pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go delete mode 100644 pkg/security/podsecuritypolicy/types.go delete mode 100644 pkg/security/podsecuritypolicy/user/doc.go delete mode 100644 pkg/security/podsecuritypolicy/user/mustrunas.go delete mode 100644 pkg/security/podsecuritypolicy/user/mustrunas_test.go delete mode 100644 pkg/security/podsecuritypolicy/user/nonroot.go delete mode 100644 pkg/security/podsecuritypolicy/user/nonroot_test.go delete mode 100644 pkg/security/podsecuritypolicy/user/runasany.go delete mode 100644 pkg/security/podsecuritypolicy/user/runasany_test.go delete mode 100644 pkg/security/podsecuritypolicy/user/types.go delete mode 100644 pkg/security/podsecuritypolicy/util/doc.go delete mode 100644 pkg/security/podsecuritypolicy/util/util.go delete mode 100644 pkg/security/podsecuritypolicy/util/util_test.go delete mode 100644 plugin/pkg/admission/security/podsecuritypolicy/OWNERS delete mode 100644 plugin/pkg/admission/security/podsecuritypolicy/admission.go delete mode 100644 plugin/pkg/admission/security/podsecuritypolicy/admission_test.go delete mode 100644 test/e2e/auth/pod_security_policy.go delete mode 100644 test/e2e/framework/psp.go diff --git a/pkg/apis/policy/validation/validation.go b/pkg/apis/policy/validation/validation.go index 53e3bbaf821..ae2d931615e 100644 --- a/pkg/apis/policy/validation/validation.go +++ b/pkg/apis/policy/validation/validation.go @@ -33,8 +33,15 @@ import ( core "k8s.io/kubernetes/pkg/apis/core" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/policy" - "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" +) + +const ( + // AllowAny is the wildcard used to allow any profile. + seccompAllowAny = "*" + // DefaultProfileAnnotationKey specifies the default seccomp profile. + seccompDefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName" + // AllowedProfilesAnnotationKey specifies the allowed seccomp profiles. + seccompAllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames" ) // ValidatePodDisruptionBudget validates a PodDisruptionBudget and returns an ErrorList @@ -149,15 +156,15 @@ func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string, } } - if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" { - allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...) + if p := annotations[seccompDefaultProfileAnnotationKey]; p != "" { + allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccompDefaultProfileAnnotationKey))...) } - if allowed := annotations[seccomp.AllowedProfilesAnnotationKey]; allowed != "" { + if allowed := annotations[seccompAllowedProfilesAnnotationKey]; allowed != "" { for _, p := range strings.Split(allowed, ",") { - if p == seccomp.AllowAny { + if p == seccompAllowAny { continue } - allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.AllowedProfilesAnnotationKey))...) + allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccompAllowedProfilesAnnotationKey))...) } } return allErrs @@ -321,7 +328,7 @@ func validatePSPSupplementalGroup(fldPath *field.Path, groupOptions *policy.Supp // validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy. func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []policy.FSType) field.ErrorList { allErrs := field.ErrorList{} - allowed := psputil.GetAllFSTypesAsSet() + allowed := getAllFSTypesAsSet() // add in the * value since that is a pseudo type that is not included by default allowed.Insert(string(policy.All)) for _, v := range volumes { @@ -332,6 +339,44 @@ func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []policy.FSTy return allErrs } +// getAllFSTypesAsSet returns all actual volume types, regardless +// of feature gates. The special policy.All pseudo type is not included. +func getAllFSTypesAsSet() sets.String { + fstypes := sets.NewString() + fstypes.Insert( + string(policy.HostPath), + string(policy.AzureFile), + string(policy.Flocker), + string(policy.FlexVolume), + string(policy.EmptyDir), + string(policy.GCEPersistentDisk), + string(policy.AWSElasticBlockStore), + string(policy.GitRepo), + string(policy.Secret), + string(policy.NFS), + string(policy.ISCSI), + string(policy.Glusterfs), + string(policy.PersistentVolumeClaim), + string(policy.RBD), + string(policy.Cinder), + string(policy.CephFS), + string(policy.DownwardAPI), + string(policy.FC), + string(policy.ConfigMap), + string(policy.VsphereVolume), + string(policy.Quobyte), + string(policy.AzureDisk), + string(policy.PhotonPersistentDisk), + string(policy.StorageOS), + string(policy.Projected), + string(policy.PortworxVolume), + string(policy.ScaleIO), + string(policy.CSI), + string(policy.Ephemeral), + ) + return fstypes +} + // validatePSPDefaultAllowPrivilegeEscalation validates the DefaultAllowPrivilegeEscalation field against the AllowPrivilegeEscalation field of a PodSecurityPolicy. func validatePSPDefaultAllowPrivilegeEscalation(fldPath *field.Path, defaultAllowPrivilegeEscalation *bool, allowPrivilegeEscalation bool) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/apis/policy/validation/validation_test.go b/pkg/apis/policy/validation/validation_test.go index bbaba5869e6..c6d154cb400 100644 --- a/pkg/apis/policy/validation/validation_test.go +++ b/pkg/apis/policy/validation/validation_test.go @@ -30,8 +30,6 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/policy" - "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" "k8s.io/utils/pointer" ) @@ -373,15 +371,15 @@ func TestValidatePodSecurityPolicy(t *testing.T) { invalidSeccompDefault := validPSP() invalidSeccompDefault.Annotations = map[string]string{ - seccomp.DefaultProfileAnnotationKey: "not-good", + seccompDefaultProfileAnnotationKey: "not-good", } invalidSeccompAllowAnyDefault := validPSP() invalidSeccompAllowAnyDefault.Annotations = map[string]string{ - seccomp.DefaultProfileAnnotationKey: "*", + seccompDefaultProfileAnnotationKey: "*", } invalidSeccompAllowed := validPSP() invalidSeccompAllowed.Annotations = map[string]string{ - seccomp.AllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",not-good", + seccompAllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",not-good", } invalidAllowedHostPathMissingPath := validPSP() @@ -660,8 +658,8 @@ func TestValidatePodSecurityPolicy(t *testing.T) { validSeccomp := validPSP() validSeccomp.Annotations = map[string]string{ - seccomp.DefaultProfileAnnotationKey: api.SeccompProfileRuntimeDefault, - seccomp.AllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",unconfined,localhost/foo,*", + seccompDefaultProfileAnnotationKey: api.SeccompProfileRuntimeDefault, + seccompAllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",unconfined,localhost/foo,*", } validDefaultAllowPrivilegeEscalation := validPSP() @@ -779,7 +777,7 @@ func TestValidatePSPVolumes(t *testing.T) { } } - volumes := psputil.GetAllFSTypesAsSet() + volumes := getAllFSTypesAsSet() // add in the * value since that is a pseudo type that is not included by default volumes.Insert(string(policy.All)) diff --git a/pkg/kubeapiserver/options/plugins.go b/pkg/kubeapiserver/options/plugins.go index 7f123dcadac..0c13dd95c8a 100644 --- a/pkg/kubeapiserver/options/plugins.go +++ b/pkg/kubeapiserver/options/plugins.go @@ -45,7 +45,6 @@ import ( podpriority "k8s.io/kubernetes/plugin/pkg/admission/priority" "k8s.io/kubernetes/plugin/pkg/admission/runtimeclass" "k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity" - "k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy" "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny" "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" "k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label" @@ -75,8 +74,7 @@ var AllOrderedPlugins = []string{ nodetaint.PluginName, // TaintNodesByCondition alwayspullimages.PluginName, // AlwaysPullImages imagepolicy.PluginName, // ImagePolicyWebhook - podsecurity.PluginName, // PodSecurity - before PodSecurityPolicy so audit/warn get exercised even if PodSecurityPolicy denies - podsecuritypolicy.PluginName, // PodSecurityPolicy + podsecurity.PluginName, // PodSecurity podnodeselector.PluginName, // PodNodeSelector podpriority.PluginName, // Priority defaulttolerationseconds.PluginName, // DefaultTolerationSeconds @@ -129,7 +127,6 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { runtimeclass.Register(plugins) resourcequota.Register(plugins) podsecurity.Register(plugins) - podsecuritypolicy.Register(plugins) podpriority.Register(plugins) scdeny.Register(plugins) serviceaccount.Register(plugins) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 01e9c49c6f1..89eff1e5cc7 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -108,7 +108,6 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/sliceutils" "k8s.io/kubernetes/pkg/kubelet/volumemanager" "k8s.io/kubernetes/pkg/security/apparmor" - sysctlallowlist "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl" "k8s.io/kubernetes/pkg/util/oom" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/csi" @@ -776,7 +775,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, // Safe, allowed sysctls can always be used as unsafe sysctls in the spec. // Hence, we concatenate those two lists. - safeAndUnsafeSysctls := append(sysctlallowlist.SafeSysctlAllowlist(), allowedUnsafeSysctls...) + safeAndUnsafeSysctls := append(sysctl.SafeSysctlAllowlist(), allowedUnsafeSysctls...) sysctlsAllowlist, err := sysctl.NewAllowlist(safeAndUnsafeSysctls) if err != nil { return nil, err diff --git a/pkg/kubelet/sysctl/allowlist_test.go b/pkg/kubelet/sysctl/allowlist_test.go index 7e7b6627a93..5d838365e4f 100644 --- a/pkg/kubelet/sysctl/allowlist_test.go +++ b/pkg/kubelet/sysctl/allowlist_test.go @@ -18,8 +18,6 @@ package sysctl import ( "testing" - - "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl" ) func TestNewAllowlist(t *testing.T) { @@ -37,7 +35,7 @@ func TestNewAllowlist(t *testing.T) { {sysctls: []string{"net.*/foo"}, err: true}, {sysctls: []string{"foo"}, err: true}, } { - _, err := NewAllowlist(append(sysctl.SafeSysctlAllowlist(), test.sysctls...)) + _, err := NewAllowlist(append(SafeSysctlAllowlist(), test.sysctls...)) if test.err && err == nil { t.Errorf("expected an error creating a allowlist for %v", test.sysctls) } else if !test.err && err != nil { @@ -69,7 +67,7 @@ func TestAllowlist(t *testing.T) { {sysctl: "kernel.sem", hostIPC: true}, } - w, err := NewAllowlist(append(sysctl.SafeSysctlAllowlist(), "kernel.msg*", "kernel.sem")) + w, err := NewAllowlist(append(SafeSysctlAllowlist(), "kernel.msg*", "kernel.sem")) if err != nil { t.Fatalf("failed to create allowlist: %v", err) } diff --git a/pkg/security/podsecuritypolicy/sysctl/types.go b/pkg/kubelet/sysctl/safe_sysctls.go similarity index 54% rename from pkg/security/podsecuritypolicy/sysctl/types.go rename to pkg/kubelet/sysctl/safe_sysctls.go index a6c2034a8d4..738846121ed 100644 --- a/pkg/security/podsecuritypolicy/sysctl/types.go +++ b/pkg/kubelet/sysctl/safe_sysctls.go @@ -16,13 +16,17 @@ limitations under the License. package sysctl -import ( - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// SysctlsStrategy defines the interface for all sysctl strategies. -type SysctlsStrategy interface { - // Validate ensures that the specified values fall within the range of the strategy. - Validate(pod *api.Pod) field.ErrorList +// SafeSysctlAllowlist returns the allowlist of safe sysctls and safe sysctl patterns (ending in *). +// +// A sysctl is called safe iff +// - it is namespaced in the container or the pod +// - it is isolated, i.e. has no influence on any other pod on the same node. +func SafeSysctlAllowlist() []string { + return []string{ + "kernel.shm_rmid_forced", + "net.ipv4.ip_local_port_range", + "net.ipv4.tcp_syncookies", + "net.ipv4.ping_group_range", + "net.ipv4.ip_unprivileged_port_start", + } } diff --git a/pkg/security/podsecuritypolicy/OWNERS b/pkg/security/podsecuritypolicy/OWNERS deleted file mode 100644 index 50dc329afb3..00000000000 --- a/pkg/security/podsecuritypolicy/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -approvers: - - sig-auth-policy-approvers -reviewers: - - sig-auth-policy-reviewers -labels: - - sig/auth diff --git a/pkg/security/podsecuritypolicy/apparmor/strategy.go b/pkg/security/podsecuritypolicy/apparmor/strategy.go deleted file mode 100644 index 31a06c9198c..00000000000 --- a/pkg/security/podsecuritypolicy/apparmor/strategy.go +++ /dev/null @@ -1,111 +0,0 @@ -/* -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 apparmor - -import ( - "fmt" - "strings" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/security/apparmor" - "k8s.io/kubernetes/pkg/util/maps" -) - -// Strategy defines the interface for all AppArmor constraint strategies. -type Strategy interface { - // Generate updates the annotations based on constraint rules. The updates are applied to a copy - // of the annotations, and returned. - Generate(annotations map[string]string, container *api.Container) (map[string]string, error) - // Validate ensures that the specified values fall within the range of the strategy. - Validate(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 -} - -var _ Strategy = &strategy{} - -// NewStrategy creates a new strategy that enforces AppArmor profile constraints. -func NewStrategy(pspAnnotations map[string]string) Strategy { - var allowedProfiles map[string]bool - if allowed, ok := pspAnnotations[v1.AppArmorBetaAllowedProfilesAnnotationKey]; ok { - profiles := strings.Split(allowed, ",") - allowedProfiles = make(map[string]bool, len(profiles)) - for _, p := range profiles { - allowedProfiles[p] = true - } - } - return &strategy{ - defaultProfile: pspAnnotations[v1.AppArmorBetaDefaultProfileAnnotationKey], - allowedProfiles: allowedProfiles, - allowedProfilesString: pspAnnotations[v1.AppArmorBetaAllowedProfilesAnnotationKey], - } -} - -func (s *strategy) Generate(annotations map[string]string, container *api.Container) (map[string]string, error) { - copy := maps.CopySS(annotations) - - if annotations[v1.AppArmorBetaContainerAnnotationKeyPrefix+container.Name] != "" { - // Profile already set, nothing to do. - return copy, nil - } - - if s.defaultProfile == "" { - // No default set. - return copy, nil - } - - if copy == nil { - copy = map[string]string{} - } - // Add the default profile. - copy[v1.AppArmorBetaContainerAnnotationKeyPrefix+container.Name] = s.defaultProfile - - return copy, nil -} - -func (s *strategy) Validate(pod *api.Pod, container *api.Container) field.ErrorList { - if s.allowedProfiles == nil { - // Unrestricted: allow all. - return nil - } - - allErrs := field.ErrorList{} - fieldPath := field.NewPath("pod", "metadata", "annotations").Key(v1.AppArmorBetaContainerAnnotationKeyPrefix + container.Name) - - profile := apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name) - if profile == "" { - if len(s.allowedProfiles) > 0 { - allErrs = append(allErrs, field.Forbidden(fieldPath, "AppArmor profile must be set")) - return allErrs - } - return nil - } - - if !s.allowedProfiles[profile] { - msg := fmt.Sprintf("%s is not an allowed profile. Allowed values: %q", profile, s.allowedProfilesString) - allErrs = append(allErrs, field.Forbidden(fieldPath, msg)) - } - - return allErrs -} diff --git a/pkg/security/podsecuritypolicy/apparmor/strategy_test.go b/pkg/security/podsecuritypolicy/apparmor/strategy_test.go deleted file mode 100644 index c1f8a3aa6a3..00000000000 --- a/pkg/security/podsecuritypolicy/apparmor/strategy_test.go +++ /dev/null @@ -1,174 +0,0 @@ -/* -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 apparmor - -import ( - "testing" - - "github.com/davecgh/go-spew/spew" - "github.com/stretchr/testify/assert" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/util/maps" -) - -const ( - containerName = "test-c" -) - -var ( - withoutAppArmor = map[string]string{"foo": "bar"} - withDefault = map[string]string{ - "foo": "bar", - v1.AppArmorBetaContainerAnnotationKeyPrefix + containerName: v1.AppArmorBetaProfileRuntimeDefault, - } - withLocal = map[string]string{ - "foo": "bar", - v1.AppArmorBetaContainerAnnotationKeyPrefix + containerName: v1.AppArmorBetaProfileNamePrefix + "foo", - } - withDisallowed = map[string]string{ - "foo": "bar", - v1.AppArmorBetaContainerAnnotationKeyPrefix + containerName: v1.AppArmorBetaProfileNamePrefix + "bad", - } - - noAppArmor = map[string]string{"foo": "bar"} - unconstrainedWithDefault = map[string]string{ - v1.AppArmorBetaDefaultProfileAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - } - constrained = map[string]string{ - v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault + "," + - v1.AppArmorBetaProfileNamePrefix + "foo", - } - constrainedWithDefault = map[string]string{ - v1.AppArmorBetaDefaultProfileAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault + "," + - v1.AppArmorBetaProfileNamePrefix + "foo", - } - - container = api.Container{ - Name: containerName, - Image: "busybox", - } -) - -func TestGenerate(t *testing.T) { - type testcase struct { - pspAnnotations map[string]string - podAnnotations map[string]string - expected map[string]string - } - tests := []testcase{{ - pspAnnotations: noAppArmor, - podAnnotations: withoutAppArmor, - expected: withoutAppArmor, - }, { - pspAnnotations: unconstrainedWithDefault, - podAnnotations: withoutAppArmor, - expected: withDefault, - }, { - pspAnnotations: constrained, - podAnnotations: withoutAppArmor, - expected: withoutAppArmor, - }, { - pspAnnotations: constrainedWithDefault, - podAnnotations: withoutAppArmor, - expected: withDefault, - }} - - // Add unchanging permutations. - for _, podAnnotations := range []map[string]string{withDefault, withLocal} { - for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} { - tests = append(tests, testcase{ - pspAnnotations: pspAnnotations, - podAnnotations: podAnnotations, - expected: podAnnotations, - }) - } - } - - for i, test := range tests { - s := NewStrategy(test.pspAnnotations) - msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)} - actual, err := s.Generate(test.podAnnotations, &container) - assert.NoError(t, err, msgAndArgs...) - assert.Equal(t, test.expected, actual, msgAndArgs...) - } -} - -func TestValidate(t *testing.T) { - type testcase struct { - pspAnnotations map[string]string - podAnnotations map[string]string - expectErr bool - } - tests := []testcase{} - // Valid combinations - for _, podAnnotations := range []map[string]string{withDefault, withLocal} { - for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} { - tests = append(tests, testcase{ - pspAnnotations: pspAnnotations, - podAnnotations: podAnnotations, - expectErr: false, - }) - } - } - for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} { - for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault} { - tests = append(tests, testcase{ - pspAnnotations: pspAnnotations, - podAnnotations: podAnnotations, - expectErr: false, - }) - } - } - // Invalid combinations - for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} { - for _, pspAnnotations := range []map[string]string{constrained, constrainedWithDefault} { - tests = append(tests, testcase{ - pspAnnotations: pspAnnotations, - podAnnotations: podAnnotations, - expectErr: true, - }) - } - } - - for i, test := range tests { - s := NewStrategy(test.pspAnnotations) - pod, container := makeTestPod(test.podAnnotations) - msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)} - errs := s.Validate(pod, container) - if test.expectErr { - assert.Len(t, errs, 1, msgAndArgs...) - } else { - assert.Len(t, errs, 0, msgAndArgs...) - } - } -} - -func makeTestPod(annotations map[string]string) (*api.Pod, *api.Container) { - return &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pod", - Annotations: maps.CopySS(annotations), - }, - Spec: api.PodSpec{ - Containers: []api.Container{container}, - }, - }, &container -} diff --git a/pkg/security/podsecuritypolicy/capabilities/capabilities.go b/pkg/security/podsecuritypolicy/capabilities/capabilities.go deleted file mode 100644 index aa8c180509c..00000000000 --- a/pkg/security/podsecuritypolicy/capabilities/capabilities.go +++ /dev/null @@ -1,165 +0,0 @@ -/* -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 capabilities - -import ( - "fmt" - - corev1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// defaultCapabilities implements the Strategy interface -type defaultCapabilities struct { - defaultAddCapabilities []api.Capability - requiredDropCapabilities []api.Capability - allowedCaps []api.Capability -} - -var _ Strategy = &defaultCapabilities{} - -// NewDefaultCapabilities creates a new defaultCapabilities strategy that will provide defaults and validation -// based on the configured initial caps and allowed caps. -func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, allowedCaps []corev1.Capability) (Strategy, error) { - internalDefaultAddCaps := make([]api.Capability, len(defaultAddCapabilities)) - for i, capability := range defaultAddCapabilities { - internalDefaultAddCaps[i] = api.Capability(capability) - } - internalRequiredDropCaps := make([]api.Capability, len(requiredDropCapabilities)) - for i, capability := range requiredDropCapabilities { - internalRequiredDropCaps[i] = api.Capability(capability) - } - internalAllowedCaps := make([]api.Capability, len(allowedCaps)) - for i, capability := range allowedCaps { - internalAllowedCaps[i] = api.Capability(capability) - } - return &defaultCapabilities{ - defaultAddCapabilities: internalDefaultAddCaps, - requiredDropCapabilities: internalRequiredDropCaps, - allowedCaps: internalAllowedCaps, - }, nil -} - -// Generate creates the capabilities based on policy rules. Generate will produce the following: -// 1. a capabilities.Add set containing all the required adds (unless the -// container specifically is dropping the cap) and container requested adds -// 2. a capabilities.Drop set containing all the required drops and container requested drops -// -// Returns the original container capabilities if no changes are required. -func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) { - defaultAdd := makeCapSet(s.defaultAddCapabilities) - requiredDrop := makeCapSet(s.requiredDropCapabilities) - containerAdd := sets.NewString() - containerDrop := sets.NewString() - - var containerCapabilities *api.Capabilities - if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { - containerCapabilities = container.SecurityContext.Capabilities - containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add) - containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop) - } - - // remove any default adds that the container is specifically dropping - defaultAdd = defaultAdd.Difference(containerDrop) - - combinedAdd := defaultAdd.Union(containerAdd) - combinedDrop := requiredDrop.Union(containerDrop) - - // no changes? return the original capabilities - if (len(combinedAdd) == len(containerAdd)) && (len(combinedDrop) == len(containerDrop)) { - return containerCapabilities, nil - } - - return &api.Capabilities{ - Add: capabilityFromStringSlice(combinedAdd.List()), - Drop: capabilityFromStringSlice(combinedDrop.List()), - }, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *defaultCapabilities) Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList { - allErrs := field.ErrorList{} - - if capabilities == nil { - // if container.SC.Caps is nil then nothing was defaulted by the strategy or requested by the pod author - // if there are no required caps on the strategy and nothing is requested on the pod - // then we can safely return here without further validation. - if len(s.defaultAddCapabilities) == 0 && len(s.requiredDropCapabilities) == 0 { - return allErrs - } - - // container has no requested caps but we have required caps. We should have something in - // at least the drops on the container. - allErrs = append(allErrs, field.Invalid(fldPath, capabilities, - "required capabilities are not set on the securityContext")) - return allErrs - } - - allowedAdd := makeCapSet(s.allowedCaps) - allowAllCaps := allowedAdd.Has(string(policy.AllowAllCapabilities)) - if allowAllCaps { - // skip validation against allowed/defaultAdd/requiredDrop because all capabilities are allowed by a wildcard - return allErrs - } - - // validate that anything being added is in the default or allowed sets - defaultAdd := makeCapSet(s.defaultAddCapabilities) - - for _, cap := range capabilities.Add { - sCap := string(cap) - if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("add"), sCap, "capability may not be added")) - } - } - - // validate that anything that is required to be dropped is in the drop set - containerDrops := makeCapSet(capabilities.Drop) - - for _, requiredDrop := range s.requiredDropCapabilities { - sDrop := string(requiredDrop) - if !containerDrops.Has(sDrop) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("drop"), capabilities.Drop, - fmt.Sprintf("%s is required to be dropped but was not found", sDrop))) - } - } - - return allErrs -} - -// capabilityFromStringSlice creates a capability slice from a string slice. -func capabilityFromStringSlice(slice []string) []api.Capability { - if len(slice) == 0 { - return nil - } - caps := []api.Capability{} - for _, c := range slice { - caps = append(caps, api.Capability(c)) - } - return caps -} - -// makeCapSet makes a string set from capabilities. -func makeCapSet(caps []api.Capability) sets.String { - s := sets.NewString() - for _, c := range caps { - s.Insert(string(c)) - } - return s -} diff --git a/pkg/security/podsecuritypolicy/capabilities/capabilities_test.go b/pkg/security/podsecuritypolicy/capabilities/capabilities_test.go deleted file mode 100644 index 21eb092333f..00000000000 --- a/pkg/security/podsecuritypolicy/capabilities/capabilities_test.go +++ /dev/null @@ -1,412 +0,0 @@ -/* -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 capabilities - -import ( - "reflect" - "testing" - - corev1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -func TestGenerateAdds(t *testing.T) { - tests := map[string]struct { - defaultAddCaps []corev1.Capability - containerCaps *api.Capabilities - expectedCaps *api.Capabilities - }{ - "no required, no container requests": {}, - "no required, no container requests, non-nil": { - containerCaps: &api.Capabilities{}, - expectedCaps: &api.Capabilities{}, - }, - "required, no container requests": { - defaultAddCaps: []corev1.Capability{"foo"}, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "required, container requests add required": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "multiple required, container requests add required": { - defaultAddCaps: []corev1.Capability{"foo", "bar", "baz"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "required, container requests add non-required": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "foo"}, - }, - }, - "generation does not mutate unnecessarily": { - defaultAddCaps: []corev1.Capability{"foo", "bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo", "foo", "bar", "baz"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo", "foo", "bar", "baz"}, - }, - }, - "generation dedupes": { - defaultAddCaps: []corev1.Capability{"foo", "bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo", "baz"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "generation is case sensitive - will not dedupe": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"FOO"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"FOO", "foo"}, - }, - }, - } - - for k, v := range tests { - container := &api.Container{ - SecurityContext: &api.SecurityContext{ - Capabilities: v.containerCaps, - }, - } - - strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, nil) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - generatedCaps, err := strategy.Generate(nil, container) - if err != nil { - t.Errorf("%s failed generating: %v", k, err) - continue - } - if v.expectedCaps == nil && generatedCaps != nil { - t.Errorf("%s expected nil caps to be generated but got %v", k, generatedCaps) - continue - } - if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { - t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) - } - } -} - -func TestGenerateDrops(t *testing.T) { - tests := map[string]struct { - defaultAddCaps []corev1.Capability - requiredDropCaps []corev1.Capability - containerCaps *api.Capabilities - expectedCaps *api.Capabilities - }{ - "no required, no container requests": { - expectedCaps: nil, - }, - "no required, no container requests, non-nil": { - containerCaps: &api.Capabilities{}, - expectedCaps: &api.Capabilities{}, - }, - "required drops are defaulted": { - requiredDropCaps: []corev1.Capability{"foo"}, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - }, - "required drops are defaulted when making container requests": { - requiredDropCaps: []corev1.Capability{"baz"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo", "bar"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "required drops do not mutate unnecessarily": { - requiredDropCaps: []corev1.Capability{"baz"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo", "bar", "baz"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"foo", "bar", "baz"}, - }, - }, - "can drop a required add": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - }, - "can drop non-required add": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"bar"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - Drop: []api.Capability{"bar"}, - }, - }, - "defaulting adds and drops, dropping a required add": { - defaultAddCaps: []corev1.Capability{"foo", "bar", "baz"}, - requiredDropCaps: []corev1.Capability{"abc"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "baz"}, - Drop: []api.Capability{"abc", "foo"}, - }, - }, - "generation dedupes": { - requiredDropCaps: []corev1.Capability{"baz", "foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"bar", "foo"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "generation is case sensitive - will not dedupe": { - requiredDropCaps: []corev1.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"BAR"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"BAR", "bar"}, - }, - }, - } - for k, v := range tests { - container := &api.Container{ - SecurityContext: &api.SecurityContext{ - Capabilities: v.containerCaps, - }, - } - - strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - generatedCaps, err := strategy.Generate(nil, container) - if err != nil { - t.Errorf("%s failed generating: %v", k, err) - continue - } - if v.expectedCaps == nil && generatedCaps != nil { - t.Errorf("%s expected nil caps to be generated but got %#v", k, generatedCaps) - continue - } - if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { - t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) - } - } -} - -func TestValidateAdds(t *testing.T) { - tests := map[string]struct { - defaultAddCaps []corev1.Capability - allowedCaps []corev1.Capability - containerCaps *api.Capabilities - expectedError string - }{ - // no container requests - "no required, no allowed, no container requests": {}, - "no required, allowed, no container requests": { - allowedCaps: []corev1.Capability{"foo"}, - }, - "required, no allowed, no container requests": { - defaultAddCaps: []corev1.Capability{"foo"}, - expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, - }, - - // container requests match required - "required, no allowed, container requests valid": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "required, no allowed, container requests invalid": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, - }, - - // container requests match allowed - "no required, allowed, container requests valid": { - allowedCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "no required, all allowed, container requests valid": { - allowedCaps: []corev1.Capability{policy.AllowAllCapabilities}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "no required, allowed, container requests invalid": { - allowedCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, - }, - - // required and allowed - "required, allowed, container requests valid required": { - defaultAddCaps: []corev1.Capability{"foo"}, - allowedCaps: []corev1.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "required, allowed, container requests valid allowed": { - defaultAddCaps: []corev1.Capability{"foo"}, - allowedCaps: []corev1.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - }, - "required, allowed, container requests invalid": { - defaultAddCaps: []corev1.Capability{"foo"}, - allowedCaps: []corev1.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"baz"}, - }, - expectedError: `capabilities.add: Invalid value: "baz": capability may not be added`, - }, - "validation is case sensitive": { - defaultAddCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"FOO"}, - }, - expectedError: `capabilities.add: Invalid value: "FOO": capability may not be added`, - }, - } - - for k, v := range tests { - strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, v.allowedCaps) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - errs := strategy.Validate(field.NewPath("capabilities"), nil, nil, v.containerCaps) - if v.expectedError == "" && len(errs) > 0 { - t.Errorf("%s should have passed but had errors %v", k, errs) - continue - } - if v.expectedError != "" && len(errs) == 0 { - t.Errorf("%s should have failed but received no errors", k) - continue - } - if len(errs) == 1 && errs[0].Error() != v.expectedError { - t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) - continue - } - if len(errs) > 1 { - t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) - } - } -} - -func TestValidateDrops(t *testing.T) { - tests := map[string]struct { - requiredDropCaps []corev1.Capability - containerCaps *api.Capabilities - expectedError string - }{ - // no container requests - "no required, no container requests": {}, - "required, no container requests": { - requiredDropCaps: []corev1.Capability{"foo"}, - expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, - }, - - // container requests match required - "required, container requests valid": { - requiredDropCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - }, - "required, container requests invalid": { - requiredDropCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"bar"}, - }, - expectedError: `capabilities.drop: Invalid value: []core.Capability{"bar"}: foo is required to be dropped but was not found`, - }, - "validation is case sensitive": { - requiredDropCaps: []corev1.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"FOO"}, - }, - expectedError: `capabilities.drop: Invalid value: []core.Capability{"FOO"}: foo is required to be dropped but was not found`, - }, - } - - for k, v := range tests { - strategy, err := NewDefaultCapabilities(nil, v.requiredDropCaps, nil) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - errs := strategy.Validate(field.NewPath("capabilities"), nil, nil, v.containerCaps) - if v.expectedError == "" && len(errs) > 0 { - t.Errorf("%s should have passed but had errors %v", k, errs) - continue - } - if v.expectedError != "" && len(errs) == 0 { - t.Errorf("%s should have failed but received no errors", k) - continue - } - if len(errs) == 1 && errs[0].Error() != v.expectedError { - t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) - continue - } - if len(errs) > 1 { - t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) - } - } -} diff --git a/pkg/security/podsecuritypolicy/capabilities/doc.go b/pkg/security/podsecuritypolicy/capabilities/doc.go deleted file mode 100644 index ff0383a270d..00000000000 --- a/pkg/security/podsecuritypolicy/capabilities/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -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 capabilities contains code for validating and defaulting a pod's -// kernel capabilities according to a security policy. -package capabilities diff --git a/pkg/security/podsecuritypolicy/capabilities/types.go b/pkg/security/podsecuritypolicy/capabilities/types.go deleted file mode 100644 index 093653bdb6e..00000000000 --- a/pkg/security/podsecuritypolicy/capabilities/types.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -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 capabilities - -import ( - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// Strategy defines the interface for all cap constraint strategies. -type Strategy interface { - // Generate creates the capabilities based on policy rules. - Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) - // Validate ensures that the specified values fall within the range of the strategy. - Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList -} diff --git a/pkg/security/podsecuritypolicy/doc.go b/pkg/security/podsecuritypolicy/doc.go deleted file mode 100644 index 3a8052c5363..00000000000 --- a/pkg/security/podsecuritypolicy/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -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 podsecuritypolicy contains code for validating and defaulting the -// security context of a pod and its containers according to a security -// policy. -package podsecuritypolicy diff --git a/pkg/security/podsecuritypolicy/factory.go b/pkg/security/podsecuritypolicy/factory.go deleted file mode 100644 index 4dd4037ec81..00000000000 --- a/pkg/security/podsecuritypolicy/factory.go +++ /dev/null @@ -1,196 +0,0 @@ -/* -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 podsecuritypolicy - -import ( - "fmt" - - corev1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/errors" - "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" -) - -type simpleStrategyFactory struct{} - -var _ StrategyFactory = &simpleStrategyFactory{} - -func NewSimpleStrategyFactory() StrategyFactory { - return &simpleStrategyFactory{} -} - -func (f *simpleStrategyFactory) CreateStrategies(psp *policy.PodSecurityPolicy, namespace string) (*ProviderStrategies, error) { - errs := []error{} - - userStrat, err := createUserStrategy(&psp.Spec.RunAsUser) - if err != nil { - errs = append(errs, err) - } - - var groupStrat group.GroupStrategy - groupStrat, err = createRunAsGroupStrategy(psp.Spec.RunAsGroup) - if err != nil { - errs = append(errs, err) - } - - seLinuxStrat, err := createSELinuxStrategy(&psp.Spec.SELinux) - if err != nil { - errs = append(errs, err) - } - - appArmorStrat, err := createAppArmorStrategy(psp) - if err != nil { - 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) - } - - supGroupStrat, err := createSupplementalGroupStrategy(&psp.Spec.SupplementalGroups) - if err != nil { - errs = append(errs, err) - } - - capStrat, err := createCapabilitiesStrategy(psp.Spec.DefaultAddCapabilities, psp.Spec.RequiredDropCapabilities, psp.Spec.AllowedCapabilities) - if err != nil { - errs = append(errs, err) - } - - sysctlsStrat := createSysctlsStrategy(sysctl.SafeSysctlAllowlist(), psp.Spec.AllowedUnsafeSysctls, psp.Spec.ForbiddenSysctls) - - if len(errs) > 0 { - return nil, errors.NewAggregate(errs) - } - - strategies := &ProviderStrategies{ - RunAsUserStrategy: userStrat, - RunAsGroupStrategy: groupStrat, - SELinuxStrategy: seLinuxStrat, - AppArmorStrategy: appArmorStrat, - FSGroupStrategy: fsGroupStrat, - SupplementalGroupStrategy: supGroupStrat, - CapabilitiesStrategy: capStrat, - SeccompStrategy: seccompStrat, - SysctlsStrategy: sysctlsStrat, - } - - return strategies, nil -} - -// createUserStrategy creates a new user strategy. -func createUserStrategy(opts *policy.RunAsUserStrategyOptions) (user.RunAsUserStrategy, error) { - switch opts.Rule { - case policy.RunAsUserStrategyMustRunAs: - return user.NewMustRunAs(opts) - case policy.RunAsUserStrategyMustRunAsNonRoot: - return user.NewRunAsNonRoot(opts) - case policy.RunAsUserStrategyRunAsAny: - return user.NewRunAsAny(opts) - default: - return nil, fmt.Errorf("Unrecognized RunAsUser strategy type %s", opts.Rule) - } -} - -// createRunAsGroupStrategy creates a new group strategy. -func createRunAsGroupStrategy(opts *policy.RunAsGroupStrategyOptions) (group.GroupStrategy, error) { - if opts == nil { - return group.NewRunAsAny() - } - switch opts.Rule { - case policy.RunAsGroupStrategyMustRunAs: - return group.NewMustRunAs(opts.Ranges) - case policy.RunAsGroupStrategyRunAsAny: - return group.NewRunAsAny() - case policy.RunAsGroupStrategyMayRunAs: - return group.NewMayRunAs(opts.Ranges) - default: - return nil, fmt.Errorf("Unrecognized RunAsGroup strategy type %s", opts.Rule) - } -} - -// createSELinuxStrategy creates a new selinux strategy. -func createSELinuxStrategy(opts *policy.SELinuxStrategyOptions) (selinux.SELinuxStrategy, error) { - switch opts.Rule { - case policy.SELinuxStrategyMustRunAs: - return selinux.NewMustRunAs(opts) - case policy.SELinuxStrategyRunAsAny: - return selinux.NewRunAsAny(opts) - default: - return nil, fmt.Errorf("Unrecognized SELinuxContext strategy type %s", opts.Rule) - } -} - -// createAppArmorStrategy creates a new AppArmor strategy. -func createAppArmorStrategy(psp *policy.PodSecurityPolicy) (apparmor.Strategy, error) { - return apparmor.NewStrategy(psp.Annotations), nil -} - -// createSeccompStrategy creates a new seccomp strategy. -func createSeccompStrategy(psp *policy.PodSecurityPolicy) (seccomp.Strategy, error) { - return seccomp.NewStrategy(psp.Annotations), nil -} - -// createFSGroupStrategy creates a new fsgroup strategy -func createFSGroupStrategy(opts *policy.FSGroupStrategyOptions) (group.GroupStrategy, error) { - switch opts.Rule { - case policy.FSGroupStrategyRunAsAny: - return group.NewRunAsAny() - case policy.FSGroupStrategyMayRunAs: - return group.NewMayRunAs(opts.Ranges) - case policy.FSGroupStrategyMustRunAs: - return group.NewMustRunAs(opts.Ranges) - default: - return nil, fmt.Errorf("Unrecognized FSGroup strategy type %s", opts.Rule) - } -} - -// createSupplementalGroupStrategy creates a new supplemental group strategy -func createSupplementalGroupStrategy(opts *policy.SupplementalGroupsStrategyOptions) (group.GroupStrategy, error) { - switch opts.Rule { - case policy.SupplementalGroupsStrategyRunAsAny: - return group.NewRunAsAny() - case policy.SupplementalGroupsStrategyMayRunAs: - return group.NewMayRunAs(opts.Ranges) - case policy.SupplementalGroupsStrategyMustRunAs: - return group.NewMustRunAs(opts.Ranges) - default: - return nil, fmt.Errorf("Unrecognized SupplementalGroups strategy type %s", opts.Rule) - } -} - -// createCapabilitiesStrategy creates a new capabilities strategy. -func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []corev1.Capability) (capabilities.Strategy, error) { - return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps) -} - -// createSysctlsStrategy creates a new sysctls strategy. -func createSysctlsStrategy(safeAllowlist, allowedUnsafeSysctls, forbiddenSysctls []string) sysctl.SysctlsStrategy { - return sysctl.NewMustMatchPatterns(safeAllowlist, allowedUnsafeSysctls, forbiddenSysctls) -} diff --git a/pkg/security/podsecuritypolicy/group/doc.go b/pkg/security/podsecuritypolicy/group/doc.go deleted file mode 100644 index 9bb177b442a..00000000000 --- a/pkg/security/podsecuritypolicy/group/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -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 group contains code for validating and defaulting the FSGroup and -// supplemental groups of a pod according to a security policy. -package group diff --git a/pkg/security/podsecuritypolicy/group/helpers.go b/pkg/security/podsecuritypolicy/group/helpers.go deleted file mode 100644 index b7f4547eab4..00000000000 --- a/pkg/security/podsecuritypolicy/group/helpers.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2018 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 group - -import ( - "fmt" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" -) - -func ValidateGroupsInRanges(fldPath *field.Path, ranges []policy.IDRange, groups []int64) field.ErrorList { - allErrs := field.ErrorList{} - - for _, group := range groups { - if !isGroupInRanges(group, ranges) { - detail := fmt.Sprintf("group %d must be in the ranges: %v", group, ranges) - allErrs = append(allErrs, field.Invalid(fldPath, groups, detail)) - } - } - return allErrs -} - -func isGroupInRanges(group int64, ranges []policy.IDRange) bool { - for _, rng := range ranges { - if psputil.GroupFallsInRange(group, rng) { - return true - } - } - return false -} diff --git a/pkg/security/podsecuritypolicy/group/mayrunas.go b/pkg/security/podsecuritypolicy/group/mayrunas.go deleted file mode 100644 index 94da43d5076..00000000000 --- a/pkg/security/podsecuritypolicy/group/mayrunas.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2018 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 group - -import ( - "fmt" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// mayRunAs implements the GroupStrategy interface. -type mayRunAs struct { - ranges []policy.IDRange -} - -var _ GroupStrategy = &mayRunAs{} - -// NewMayRunAs provides a new MayRunAs strategy. -func NewMayRunAs(ranges []policy.IDRange) (GroupStrategy, error) { - if len(ranges) == 0 { - return nil, fmt.Errorf("ranges must be supplied for MayRunAs") - } - return &mayRunAs{ - ranges: ranges, - }, nil -} - -// Generate creates the group based on policy rules. This strategy returns an empty slice. -func (s *mayRunAs) Generate(_ *api.Pod) ([]int64, error) { - return nil, nil -} - -// Generate a single value to be applied. This is used for FSGroup. This strategy returns nil. -func (s *mayRunAs) GenerateSingle(_ *api.Pod) (*int64, error) { - return nil, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -// Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and -// supplemental groups). -func (s *mayRunAs) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) field.ErrorList { - return ValidateGroupsInRanges(fldPath, s.ranges, groups) -} diff --git a/pkg/security/podsecuritypolicy/group/mayrunas_test.go b/pkg/security/podsecuritypolicy/group/mayrunas_test.go deleted file mode 100644 index 506a7a8b99e..00000000000 --- a/pkg/security/podsecuritypolicy/group/mayrunas_test.go +++ /dev/null @@ -1,185 +0,0 @@ -/* -Copyright 2018 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 group - -import ( - "fmt" - "strings" - "testing" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -func TestMayRunAsOptions(t *testing.T) { - tests := map[string]struct { - ranges []policy.IDRange - pass bool - }{ - "empty": { - ranges: []policy.IDRange{}, - }, - "ranges": { - ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - }, - pass: true, - }, - } - - for k, v := range tests { - _, err := NewMayRunAs(v.ranges) - if v.pass && err != nil { - t.Errorf("error creating strategy for %s: %v", k, err) - } - if !v.pass && err == nil { - t.Errorf("expected error for %s but got none", k) - } - } -} - -func TestMayRunAsValidate(t *testing.T) { - tests := map[string]struct { - ranges []policy.IDRange - groups []int64 - expectedErrors []string - }{ - "empty groups": { - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - }, - "not in range": { - groups: []int64{5}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - {Min: 4, Max: 4}, - }, - expectedErrors: []string{"group 5 must be in the ranges: [{1 3} {4 4}]"}, - }, - "not in ranges - multiple groups": { - groups: []int64{5, 10, 2020}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - {Min: 15, Max: 70}, - }, - expectedErrors: []string{ - "group 5 must be in the ranges: [{1 3} {15 70}]", - "group 10 must be in the ranges: [{1 3} {15 70}]", - "group 2020 must be in the ranges: [{1 3} {15 70}]", - }, - }, - "not in ranges - one of multiple groups does not match": { - groups: []int64{5, 10, 2020}, - ranges: []policy.IDRange{ - {Min: 1, Max: 5}, - {Min: 8, Max: 12}, - {Min: 15, Max: 70}, - }, - expectedErrors: []string{ - "group 2020 must be in the ranges: [{1 5} {8 12} {15 70}]", - }, - }, - "in range 1": { - groups: []int64{2}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - }, - "in range boundary min": { - groups: []int64{1}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - }, - "in range boundary max": { - groups: []int64{3}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - }, - "singular range": { - groups: []int64{4}, - ranges: []policy.IDRange{ - {Min: 4, Max: 4}, - }, - }, - "in one of multiple ranges": { - groups: []int64{4}, - ranges: []policy.IDRange{ - {Min: 1, Max: 4}, - {Min: 10, Max: 15}, - }, - }, - "multiple groups matches one range": { - groups: []int64{4, 8, 12}, - ranges: []policy.IDRange{ - {Min: 1, Max: 20}, - }, - }, - "multiple groups match multiple ranges": { - groups: []int64{4, 8, 12}, - ranges: []policy.IDRange{ - {Min: 1, Max: 4}, - {Min: 200, Max: 2000}, - {Min: 7, Max: 11}, - {Min: 5, Max: 7}, - {Min: 17, Max: 53}, - {Min: 12, Max: 71}, - }, - }, - } - - for k, v := range tests { - s, err := NewMayRunAs(v.ranges) - if err != nil { - t.Errorf("error creating strategy for %s: %v", k, err) - } - errs := s.Validate(field.NewPath(""), nil, v.groups) - if len(v.expectedErrors) != len(errs) { - // number of expected errors is different from actual, includes cases when we expected errors and they appeared or vice versa - t.Errorf("number of expected errors for '%s' does not match with errors received:\n"+ - "expected:\n%s\nbut got:\n%s", - k, concatenateStrings(v.expectedErrors), concatenateErrors(errs)) - } else if len(v.expectedErrors) > 0 { - // check that the errors received match the expectations - for i, s := range v.expectedErrors { - if !strings.Contains(errs[i].Error(), s) { - t.Errorf("expected errors in particular order for '%s':\n%s\nbut got:\n%s", - k, concatenateStrings(v.expectedErrors), concatenateErrors(errs)) - break - } - } - } - } -} - -func concatenateErrors(errs field.ErrorList) string { - var errStrings []string - for _, e := range errs { - errStrings = append(errStrings, e.Error()) - } - return concatenateStrings(errStrings) -} - -func concatenateStrings(ss []string) string { - var ret string - for i, v := range ss { - ret += fmt.Sprintf("%d: %s\n", i+1, v) - } - return ret -} diff --git a/pkg/security/podsecuritypolicy/group/mustrunas.go b/pkg/security/podsecuritypolicy/group/mustrunas.go deleted file mode 100644 index 9f78d0d9015..00000000000 --- a/pkg/security/podsecuritypolicy/group/mustrunas.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -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 group - -import ( - "fmt" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// mustRunAs implements the GroupStrategy interface -type mustRunAs struct { - ranges []policy.IDRange -} - -var _ GroupStrategy = &mustRunAs{} - -// NewMustRunAs provides a new MustRunAs strategy based on ranges. -func NewMustRunAs(ranges []policy.IDRange) (GroupStrategy, error) { - if len(ranges) == 0 { - return nil, fmt.Errorf("ranges must be supplied for MustRunAs") - } - return &mustRunAs{ - ranges: ranges, - }, nil -} - -// Generate creates the group based on policy rules. By default this returns the first group of the -// first range (min val). -func (s *mustRunAs) Generate(_ *api.Pod) ([]int64, error) { - return []int64{s.ranges[0].Min}, nil -} - -// Generate a single value to be applied. This is used for FSGroup. This strategy will return -// the first group of the first range (min val). -func (s *mustRunAs) GenerateSingle(_ *api.Pod) (*int64, error) { - single := new(int64) - *single = s.ranges[0].Min - return single, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -// Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and -// supplemental groups). -func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) field.ErrorList { - allErrs := field.ErrorList{} - - if len(groups) == 0 && len(s.ranges) > 0 { - allErrs = append(allErrs, field.Invalid(fldPath, groups, "unable to validate empty groups against required ranges")) - } - - allErrs = append(allErrs, ValidateGroupsInRanges(fldPath, s.ranges, groups)...) - - return allErrs -} diff --git a/pkg/security/podsecuritypolicy/group/mustrunas_test.go b/pkg/security/podsecuritypolicy/group/mustrunas_test.go deleted file mode 100644 index 5578fac99ce..00000000000 --- a/pkg/security/podsecuritypolicy/group/mustrunas_test.go +++ /dev/null @@ -1,180 +0,0 @@ -/* -Copyright 2014 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 group - -import ( - "strings" - "testing" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -func TestMustRunAsOptions(t *testing.T) { - tests := map[string]struct { - ranges []policy.IDRange - pass bool - }{ - "empty": { - ranges: []policy.IDRange{}, - }, - "ranges": { - ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - }, - pass: true, - }, - } - - for k, v := range tests { - _, err := NewMustRunAs(v.ranges) - if v.pass && err != nil { - t.Errorf("error creating strategy for %s: %v", k, err) - } - if !v.pass && err == nil { - t.Errorf("expected error for %s but got none", k) - } - } -} - -func TestGenerate(t *testing.T) { - tests := map[string]struct { - ranges []policy.IDRange - expected []int64 - }{ - "multi value": { - ranges: []policy.IDRange{ - {Min: 1, Max: 2}, - }, - expected: []int64{1}, - }, - "single value": { - ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - }, - expected: []int64{1}, - }, - "multi range": { - ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - {Min: 2, Max: 500}, - }, - expected: []int64{1}, - }, - } - - for k, v := range tests { - s, err := NewMustRunAs(v.ranges) - if err != nil { - t.Errorf("error creating strategy for %s: %v", k, err) - } - actual, err := s.Generate(nil) - if err != nil { - t.Errorf("unexpected error for %s: %v", k, err) - } - if len(actual) != len(v.expected) { - t.Errorf("unexpected generated values. Expected %v, got %v", v.expected, actual) - continue - } - if len(actual) > 0 && len(v.expected) > 0 { - if actual[0] != v.expected[0] { - t.Errorf("unexpected generated values. Expected %v, got %v", v.expected, actual) - } - } - - single, err := s.GenerateSingle(nil) - if err != nil { - t.Errorf("unexpected error for %s: %v", k, err) - } - if single == nil { - t.Errorf("unexpected nil generated value for %s: %v", k, single) - } - if *single != v.expected[0] { - t.Errorf("unexpected generated single value. Expected %v, got %v", v.expected, actual) - } - } -} - -func TestValidate(t *testing.T) { - tests := map[string]struct { - ranges []policy.IDRange - groups []int64 - expectedError string - }{ - "nil security context": { - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - expectedError: "unable to validate empty groups against required ranges", - }, - "empty groups": { - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - expectedError: "unable to validate empty groups against required ranges", - }, - "not in range": { - groups: []int64{5}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - {Min: 4, Max: 4}, - }, - expectedError: "group 5 must be in the ranges: [{1 3} {4 4}]", - }, - "in range 1": { - groups: []int64{2}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - }, - "in range boundary min": { - groups: []int64{1}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - }, - "in range boundary max": { - groups: []int64{3}, - ranges: []policy.IDRange{ - {Min: 1, Max: 3}, - }, - }, - "singular range": { - groups: []int64{4}, - ranges: []policy.IDRange{ - {Min: 4, Max: 4}, - }, - }, - } - - for k, v := range tests { - s, err := NewMustRunAs(v.ranges) - if err != nil { - t.Errorf("error creating strategy for %s: %v", k, err) - } - errs := s.Validate(field.NewPath(""), nil, v.groups) - if v.expectedError == "" && len(errs) > 0 { - t.Errorf("unexpected errors for %s: %v", k, errs) - } - if v.expectedError != "" && len(errs) == 0 { - t.Errorf("expected errors for %s but got: %v", k, errs) - } - if v.expectedError != "" && len(errs) > 0 && !strings.Contains(errs[0].Error(), v.expectedError) { - t.Errorf("expected error for %s: %v, but got: %v", k, v.expectedError, errs[0]) - } - } -} diff --git a/pkg/security/podsecuritypolicy/group/runasany.go b/pkg/security/podsecuritypolicy/group/runasany.go deleted file mode 100644 index 4fb9e86f330..00000000000 --- a/pkg/security/podsecuritypolicy/group/runasany.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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 group - -import ( - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// runAsAny implements the GroupStrategy interface. -type runAsAny struct { -} - -var _ GroupStrategy = &runAsAny{} - -// NewRunAsAny provides a new RunAsAny strategy. -func NewRunAsAny() (GroupStrategy, error) { - return &runAsAny{}, nil -} - -// Generate creates the group based on policy rules. This strategy returns an empty slice. -func (s *runAsAny) Generate(_ *api.Pod) ([]int64, error) { - return nil, nil -} - -// Generate a single value to be applied. This is used for FSGroup. This strategy returns nil. -func (s *runAsAny) GenerateSingle(_ *api.Pod) (*int64, error) { - return nil, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) field.ErrorList { - return field.ErrorList{} - -} diff --git a/pkg/security/podsecuritypolicy/group/runasany_test.go b/pkg/security/podsecuritypolicy/group/runasany_test.go deleted file mode 100644 index f7f1b40f332..00000000000 --- a/pkg/security/podsecuritypolicy/group/runasany_test.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -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 group - -import ( - "testing" - - "k8s.io/apimachinery/pkg/util/validation/field" -) - -func TestRunAsAnyGenerate(t *testing.T) { - s, err := NewRunAsAny() - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - groups, err := s.Generate(nil) - if len(groups) > 0 { - t.Errorf("expected empty but got %v", groups) - } - if err != nil { - t.Errorf("unexpected error generating groups: %v", err) - } -} - -func TestRunAsAnyGenerateSingle(t *testing.T) { - s, err := NewRunAsAny() - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - group, err := s.GenerateSingle(nil) - if group != nil { - t.Errorf("expected empty but got %v", group) - } - if err != nil { - t.Errorf("unexpected error generating groups: %v", err) - } -} - -func TestRunAsAnyValidate(t *testing.T) { - s, err := NewRunAsAny() - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - errs := s.Validate(field.NewPath(""), nil, nil) - if len(errs) != 0 { - t.Errorf("unexpected errors: %v", errs) - } -} diff --git a/pkg/security/podsecuritypolicy/group/types.go b/pkg/security/podsecuritypolicy/group/types.go deleted file mode 100644 index 479469ba119..00000000000 --- a/pkg/security/podsecuritypolicy/group/types.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 group - -import ( - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// GroupStrategy defines the interface for all group constraint strategies. -type GroupStrategy interface { - // Generate creates the group based on policy rules. The underlying implementation can - // decide whether it will return a full range of values or a subset of values from the - // configured ranges. - Generate(pod *api.Pod) ([]int64, error) - // Generate a single value to be applied. The underlying implementation decides which - // value to return if configured with multiple ranges. This is used for FSGroup. - GenerateSingle(pod *api.Pod) (*int64, error) - // Validate ensures that the specified values fall within the range of the strategy. - Validate(fldPath *field.Path, pod *api.Pod, groups []int64) field.ErrorList -} diff --git a/pkg/security/podsecuritypolicy/provider.go b/pkg/security/podsecuritypolicy/provider.go deleted file mode 100644 index eda587533c9..00000000000 --- a/pkg/security/podsecuritypolicy/provider.go +++ /dev/null @@ -1,459 +0,0 @@ -/* -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 podsecuritypolicy - -import ( - "fmt" - "strings" - - corev1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation/field" - utilfeature "k8s.io/apiserver/pkg/util/feature" - podutil "k8s.io/kubernetes/pkg/api/pod" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/core/pods" - "k8s.io/kubernetes/pkg/features" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" - "k8s.io/kubernetes/pkg/securitycontext" -) - -// simpleProvider is the default implementation of Provider. -type simpleProvider struct { - psp *policy.PodSecurityPolicy - strategies *ProviderStrategies -} - -// ensure we implement the interface correctly. -var _ Provider = &simpleProvider{} - -// NewSimpleProvider creates a new Provider instance. -func NewSimpleProvider(psp *policy.PodSecurityPolicy, namespace string, strategyFactory StrategyFactory) (Provider, error) { - if psp == nil { - return nil, fmt.Errorf("NewSimpleProvider requires a PodSecurityPolicy") - } - if strategyFactory == nil { - return nil, fmt.Errorf("NewSimpleProvider requires a StrategyFactory") - } - - strategies, err := strategyFactory.CreateStrategies(psp, namespace) - if err != nil { - return nil, err - } - - return &simpleProvider{ - psp: psp, - strategies: strategies, - }, nil -} - -// MutatePod sets the default values of the required but not filled fields. -// Validation should be used after the context is defaulted to ensure it -// complies with the required restrictions. -func (s *simpleProvider) MutatePod(pod *api.Pod) error { - sc := securitycontext.NewPodSecurityContextMutator(pod.Spec.SecurityContext) - - if sc.SupplementalGroups() == nil { - supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod) - if err != nil { - return err - } - sc.SetSupplementalGroups(supGroups) - } - - if sc.FSGroup() == nil { - fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod) - if err != nil { - return err - } - sc.SetFSGroup(fsGroup) - } - - if sc.SELinuxOptions() == nil { - seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil) - if err != nil { - return err - } - sc.SetSELinuxOptions(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(pod.Annotations, pod) - if err != nil { - return err - } - if seccompProfile != "" { - if pod.Annotations == nil { - pod.Annotations = map[string]string{} - } - pod.Annotations[api.SeccompPodAnnotationKey] = seccompProfile - } - - pod.Spec.SecurityContext = sc.PodSecurityContext() - - if s.psp.Spec.RuntimeClass != nil && pod.Spec.RuntimeClassName == nil { - pod.Spec.RuntimeClassName = s.psp.Spec.RuntimeClass.DefaultRuntimeClassName - } - - var retErr error - podutil.VisitContainers(&pod.Spec, podutil.AllContainers, func(c *api.Container, containerType podutil.ContainerType) bool { - retErr = s.mutateContainer(pod, c) - if retErr != nil { - return false - } - return true - }) - - return retErr -} - -// mutateContainer sets the default values of the required but not filled fields. -// It modifies the SecurityContext of the container and annotations of the pod. Validation should -// be used after the context is defaulted to ensure it complies with the required restrictions. -func (s *simpleProvider) mutateContainer(pod *api.Pod, container *api.Container) error { - sc := securitycontext.NewEffectiveContainerSecurityContextMutator( - securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext), - securitycontext.NewContainerSecurityContextMutator(container.SecurityContext), - ) - - if sc.RunAsUser() == nil { - uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container) - if err != nil { - return err - } - sc.SetRunAsUser(uid) - } - - if sc.RunAsGroup() == nil { - gid, err := s.strategies.RunAsGroupStrategy.GenerateSingle(pod) - if err != nil { - return err - } - sc.SetRunAsGroup(gid) - } - - if sc.SELinuxOptions() == nil { - seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container) - if err != nil { - return err - } - sc.SetSELinuxOptions(seLinux) - } - - annotations, err := s.strategies.AppArmorStrategy.Generate(pod.Annotations, container) - if err != nil { - return err - } - - // if we're using the non-root strategy set the marker that this container should not be - // run as root which will signal to the kubelet to do a final check either on the runAsUser - // or, if runAsUser is not set, the image UID will be checked. - if sc.RunAsNonRoot() == nil && sc.RunAsUser() == nil && s.psp.Spec.RunAsUser.Rule == policy.RunAsUserStrategyMustRunAsNonRoot { - nonRoot := true - sc.SetRunAsNonRoot(&nonRoot) - } - - caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container) - if err != nil { - return err - } - sc.SetCapabilities(caps) - - // if the PSP requires a read only root filesystem and the container has not made a specific - // request then default ReadOnlyRootFilesystem to true. - if s.psp.Spec.ReadOnlyRootFilesystem && sc.ReadOnlyRootFilesystem() == nil { - readOnlyRootFS := true - sc.SetReadOnlyRootFilesystem(&readOnlyRootFS) - } - - // if the PSP sets DefaultAllowPrivilegeEscalation and the container security context - // allowPrivilegeEscalation is not set, then default to that set by the PSP. - if s.psp.Spec.DefaultAllowPrivilegeEscalation != nil && sc.AllowPrivilegeEscalation() == nil { - sc.SetAllowPrivilegeEscalation(s.psp.Spec.DefaultAllowPrivilegeEscalation) - } - - // if the PSP sets psp.AllowPrivilegeEscalation to false, set that as the default - if !*s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation() == nil { - sc.SetAllowPrivilegeEscalation(s.psp.Spec.AllowPrivilegeEscalation) - } - - pod.Annotations = annotations - container.SecurityContext = sc.ContainerSecurityContext() - - return nil -} - -// ValidatePod ensure a pod is in compliance with the given constraints. -func (s *simpleProvider) ValidatePod(pod *api.Pod) field.ErrorList { - allErrs := field.ErrorList{} - - sc := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext) - scPath := field.NewPath("spec", "securityContext") - - var fsGroups []int64 - if fsGroup := sc.FSGroup(); fsGroup != nil { - fsGroups = []int64{*fsGroup} - } - allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(scPath.Child("fsGroup"), pod, fsGroups)...) - allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(scPath.Child("supplementalGroups"), pod, sc.SupplementalGroups())...) - allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...) - - allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(scPath.Child("seLinuxOptions"), pod, nil, sc.SELinuxOptions())...) - - if !s.psp.Spec.HostNetwork && sc.HostNetwork() { - allErrs = append(allErrs, field.Invalid(scPath.Child("hostNetwork"), sc.HostNetwork(), "Host network is not allowed to be used")) - } - - if !s.psp.Spec.HostPID && sc.HostPID() { - allErrs = append(allErrs, field.Invalid(scPath.Child("hostPID"), sc.HostPID(), "Host PID is not allowed to be used")) - } - - if !s.psp.Spec.HostIPC && sc.HostIPC() { - allErrs = append(allErrs, field.Invalid(scPath.Child("hostIPC"), sc.HostIPC(), "Host IPC is not allowed to be used")) - } - - allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...) - - allErrs = append(allErrs, s.validatePodVolumes(pod)...) - - if s.psp.Spec.RuntimeClass != nil { - allErrs = append(allErrs, validateRuntimeClassName(pod.Spec.RuntimeClassName, s.psp.Spec.RuntimeClass.AllowedRuntimeClassNames)...) - } - - pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool { - allErrs = append(allErrs, s.validateContainer(pod, c, p)...) - return true - }) - - return allErrs -} - -func (s *simpleProvider) validatePodVolumes(pod *api.Pod) field.ErrorList { - allErrs := field.ErrorList{} - - if len(pod.Spec.Volumes) > 0 { - allowsAllVolumeTypes := psputil.PSPAllowsAllVolumes(s.psp) - allowedVolumes := psputil.FSTypeToStringSet(s.psp.Spec.Volumes) - for i, v := range pod.Spec.Volumes { - fsType, err := psputil.GetVolumeFSType(v) - if err != nil { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "volumes").Index(i), string(fsType), err.Error())) - continue - } - - if !allowsAllVolumeTypes && !allowsVolumeType(allowedVolumes, fsType, v) { - allErrs = append(allErrs, field.Invalid( - field.NewPath("spec", "volumes").Index(i), string(fsType), - fmt.Sprintf("%s volumes are not allowed to be used", string(fsType)))) - continue - } - - switch fsType { - case policy.HostPath: - allows, mustBeReadOnly := psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path) - if !allows { - allErrs = append(allErrs, field.Invalid( - field.NewPath("spec", "volumes").Index(i).Child("hostPath", "pathPrefix"), v.HostPath.Path, - fmt.Sprintf("is not allowed to be used"))) - } else if mustBeReadOnly { - // Ensure all the VolumeMounts that use this volume are read-only - pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool { - for i, cv := range c.VolumeMounts { - if cv.Name == v.Name && !cv.ReadOnly { - allErrs = append(allErrs, field.Invalid(p.Child("volumeMounts").Index(i).Child("readOnly"), cv.ReadOnly, "must be read-only")) - } - } - return true - }) - } - - case policy.FlexVolume: - if len(s.psp.Spec.AllowedFlexVolumes) > 0 { - found := false - driver := v.FlexVolume.Driver - for _, allowedFlexVolume := range s.psp.Spec.AllowedFlexVolumes { - if driver == allowedFlexVolume.Driver { - found = true - break - } - } - if !found { - allErrs = append(allErrs, - field.Invalid(field.NewPath("spec", "volumes").Index(i).Child("driver"), driver, - "Flexvolume driver is not allowed to be used")) - } - } - - case policy.CSI: - if utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { - if len(s.psp.Spec.AllowedCSIDrivers) > 0 { - found := false - driver := v.CSI.Driver - for _, allowedCSIDriver := range s.psp.Spec.AllowedCSIDrivers { - if driver == allowedCSIDriver.Name { - found = true - break - } - } - if !found { - allErrs = append(allErrs, - field.Invalid(field.NewPath("spec", "volumes").Index(i).Child("csi", "driver"), driver, - "Inline CSI driver is not allowed to be used")) - } - } - } - } - } - } - - return allErrs -} - -// Ensure a container's SecurityContext is in compliance with the given constraints -func (s *simpleProvider) validateContainer(pod *api.Pod, container *api.Container, containerPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - podSC := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext) - sc := securitycontext.NewEffectiveContainerSecurityContextAccessor(podSC, securitycontext.NewContainerSecurityContextMutator(container.SecurityContext)) - - scPath := containerPath.Child("securityContext") - allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(scPath, pod, container, sc.RunAsNonRoot(), sc.RunAsUser())...) - var runAsGroups []int64 - if sc.RunAsGroup() != nil { - runAsGroups = []int64{*sc.RunAsGroup()} - } - allErrs = append(allErrs, s.strategies.RunAsGroupStrategy.Validate(scPath, pod, runAsGroups)...) - - allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(scPath.Child("seLinuxOptions"), pod, container, sc.SELinuxOptions())...) - allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...) - allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...) - - privileged := sc.Privileged() - if !s.psp.Spec.Privileged && privileged != nil && *privileged { - allErrs = append(allErrs, field.Invalid(scPath.Child("privileged"), *privileged, "Privileged containers are not allowed")) - } - - procMount := sc.ProcMount() - allowedProcMounts := s.psp.Spec.AllowedProcMountTypes - if len(allowedProcMounts) == 0 { - allowedProcMounts = []corev1.ProcMountType{corev1.DefaultProcMount} - } - foundProcMountType := false - for _, pm := range allowedProcMounts { - if string(pm) == string(procMount) { - foundProcMountType = true - } - } - - if !foundProcMountType { - allErrs = append(allErrs, field.Invalid(scPath.Child("procMount"), procMount, "ProcMountType is not allowed")) - } - - allErrs = append(allErrs, s.strategies.CapabilitiesStrategy.Validate(scPath.Child("capabilities"), pod, container, sc.Capabilities())...) - - allErrs = append(allErrs, s.hasInvalidHostPort(container, containerPath)...) - - if s.psp.Spec.ReadOnlyRootFilesystem { - readOnly := sc.ReadOnlyRootFilesystem() - if readOnly == nil { - allErrs = append(allErrs, field.Invalid(scPath.Child("readOnlyRootFilesystem"), readOnly, "ReadOnlyRootFilesystem may not be nil and must be set to true")) - } else if !*readOnly { - allErrs = append(allErrs, field.Invalid(scPath.Child("readOnlyRootFilesystem"), *readOnly, "ReadOnlyRootFilesystem must be set to true")) - } - } - - allowEscalation := sc.AllowPrivilegeEscalation() - if !*s.psp.Spec.AllowPrivilegeEscalation && (allowEscalation == nil || *allowEscalation) { - allErrs = append(allErrs, field.Invalid(scPath.Child("allowPrivilegeEscalation"), allowEscalation, "Allowing privilege escalation for containers is not allowed")) - } - - return allErrs -} - -// hasInvalidHostPort checks whether the port definitions on the container fall outside of the ranges allowed by the PSP. -func (s *simpleProvider) hasInvalidHostPort(container *api.Container, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for _, cp := range container.Ports { - if cp.HostPort > 0 && !s.isValidHostPort(cp.HostPort) { - detail := fmt.Sprintf("Host port %d is not allowed to be used. Allowed ports: [%s]", cp.HostPort, hostPortRangesToString(s.psp.Spec.HostPorts)) - allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPort"), cp.HostPort, detail)) - } - } - return allErrs -} - -// isValidHostPort returns true if the port falls in any range allowed by the PSP. -func (s *simpleProvider) isValidHostPort(port int32) bool { - for _, hostPortRange := range s.psp.Spec.HostPorts { - if port >= hostPortRange.Min && port <= hostPortRange.Max { - return true - } - } - return false -} - -// Get the name of the PSP that this provider was initialized with. -func (s *simpleProvider) GetPSPName() string { - return s.psp.Name -} - -func hostPortRangesToString(ranges []policy.HostPortRange) string { - formattedString := "" - if ranges != nil { - strRanges := []string{} - for _, r := range ranges { - if r.Min == r.Max { - strRanges = append(strRanges, fmt.Sprintf("%d", r.Min)) - } else { - strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max)) - } - } - formattedString = strings.Join(strRanges, ",") - } - return formattedString -} - -// validates that the actual RuntimeClassName is contained in the list of valid names. -func validateRuntimeClassName(actual *string, validNames []string) field.ErrorList { - if actual == nil { - return nil // An unset RuntimeClassName is always allowed. - } - - for _, valid := range validNames { - if valid == policy.AllowAllRuntimeClassNames { - return nil - } - if *actual == valid { - return nil - } - } - return field.ErrorList{field.Invalid(field.NewPath("spec", "runtimeClassName"), *actual, "")} -} - -func allowsVolumeType(allowedVolumes sets.String, fsType policy.FSType, volume api.Volume) bool { - if allowedVolumes.Has(string(fsType)) { - return true - } - - // if secret volume is allowed, all the projected volume sources that projected service account token volumes expose are allowed, regardless of psp. - if allowedVolumes.Has(string(policy.Secret)) && fsType == policy.Projected && psputil.IsOnlyServiceAccountTokenSources(volume.VolumeSource.Projected) { - return true - } - return false -} diff --git a/pkg/security/podsecuritypolicy/provider_test.go b/pkg/security/podsecuritypolicy/provider_test.go deleted file mode 100644 index 71873db3fec..00000000000 --- a/pkg/security/podsecuritypolicy/provider_test.go +++ /dev/null @@ -1,1727 +0,0 @@ -/* -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 podsecuritypolicy - -import ( - "fmt" - "reflect" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - v1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/diff" - utilfeature "k8s.io/apiserver/pkg/util/feature" - featuregatetesting "k8s.io/component-base/featuregate/testing" - api "k8s.io/kubernetes/pkg/apis/core" - k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/security/apparmor" - "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" - "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" - "k8s.io/utils/pointer" -) - -const defaultContainerName = "test-c" - -func TestMutatePodNonmutating(t *testing.T) { - // Create a pod with a security context that needs filling in - createPod := func() *api.Pod { - return &api.Pod{ - Spec: api.PodSpec{ - SecurityContext: &api.PodSecurityContext{}, - }, - } - } - - // Create a PSP with strategies that will populate a blank psc - allowPrivilegeEscalation := true - createPSP := func() *policy.PodSecurityPolicy { - return &policy.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "psp-sa", - Annotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: "*", - }, - }, - Spec: policy.PodSecurityPolicySpec{ - AllowPrivilegeEscalation: &allowPrivilegeEscalation, - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyRunAsAny, - }, - RunAsGroup: &policy.RunAsGroupStrategyOptions{ - Rule: policy.RunAsGroupStrategyRunAsAny, - }, - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyRunAsAny, - }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyRunAsAny, - }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyRunAsAny, - }, - }, - } - } - - pod := createPod() - psp := createPSP() - - provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "unable to create provider") - err = provider.MutatePod(pod) - require.NoError(t, err, "unable to modify pod") - - // Creating the provider or the security context should not have mutated the psp or pod - // since all the strategies were permissive - if !reflect.DeepEqual(createPod(), pod) { - diffs := diff.ObjectDiff(createPod(), pod) - t.Errorf("pod was mutated by MutatePod. diff:\n%s", diffs) - } - if !reflect.DeepEqual(createPSP(), psp) { - t.Error("psp was mutated by MutatePod") - } -} - -func TestMutateContainerNonmutating(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)() - - untrue := false - tests := []struct { - security *api.SecurityContext - }{ - {nil}, - {&api.SecurityContext{RunAsNonRoot: &untrue}}, - } - - for _, tc := range tests { - // Create a pod with a security context that needs filling in - createPod := func() *api.Pod { - return &api.Pod{ - Spec: api.PodSpec{ - Containers: []api.Container{{ - SecurityContext: tc.security, - }}, - EphemeralContainers: []api.EphemeralContainer{{ - EphemeralContainerCommon: api.EphemeralContainerCommon{ - SecurityContext: tc.security, - }, - }}, - }, - } - } - - // Create a PSP with strategies that will populate a blank security context - allowPrivilegeEscalation := true - createPSP := func() *policy.PodSecurityPolicy { - return &policy.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "psp-sa", - Annotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: "*", - }, - }, - Spec: policy.PodSecurityPolicySpec{ - AllowPrivilegeEscalation: &allowPrivilegeEscalation, - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyRunAsAny, - }, - RunAsGroup: &policy.RunAsGroupStrategyOptions{ - Rule: policy.RunAsGroupStrategyRunAsAny, - }, - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyRunAsAny, - }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyRunAsAny, - }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyRunAsAny, - }, - }, - } - } - - pod := createPod() - psp := createPSP() - - provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "unable to create provider") - err = provider.MutatePod(pod) - require.NoError(t, err, "unable to modify pod") - - // Creating the provider or the security context should not have mutated the psp or pod - // since all the strategies were permissive - if !reflect.DeepEqual(createPod(), pod) { - diffs := diff.ObjectDiff(createPod(), pod) - t.Errorf("pod was mutated. diff:\n%s", diffs) - } - if !reflect.DeepEqual(createPSP(), psp) { - t.Error("psp was mutated") - } - } -} - -func TestValidatePodFailures(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() - - failHostNetworkPod := defaultPod() - failHostNetworkPod.Spec.SecurityContext.HostNetwork = true - - failHostPIDPod := defaultPod() - failHostPIDPod.Spec.SecurityContext.HostPID = true - - failHostIPCPod := defaultPod() - failHostIPCPod.Spec.SecurityContext.HostIPC = true - - failSupplementalGroupPod := defaultPod() - failSupplementalGroupPod.Spec.SecurityContext.SupplementalGroups = []int64{999} - failSupplementalGroupMustPSP := defaultPSP() - failSupplementalGroupMustPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - }, - } - failSupplementalGroupMayPSP := defaultPSP() - failSupplementalGroupMayPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyMayRunAs, - Ranges: []policy.IDRange{ - {Min: 50, Max: 50}, - {Min: 55, Max: 998}, - {Min: 1000, Max: 1000}, - }, - } - - failFSGroupPod := defaultPod() - fsGroup := int64(999) - failFSGroupPod.Spec.SecurityContext.FSGroup = &fsGroup - failFSGroupMustPSP := defaultPSP() - failFSGroupMustPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - }, - } - failFSGroupMayPSP := defaultPSP() - failFSGroupMayPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyMayRunAs, - Ranges: []policy.IDRange{ - {Min: 10, Max: 20}, - {Min: 1000, Max: 1001}, - }, - } - - failNilSELinuxPod := defaultPod() - failSELinuxPSP := defaultPSP() - failSELinuxPSP.Spec.SELinux.Rule = policy.SELinuxStrategyMustRunAs - failSELinuxPSP.Spec.SELinux.SELinuxOptions = &v1.SELinuxOptions{ - Level: "foo", - } - - failInvalidSELinuxPod := defaultPod() - failInvalidSELinuxPod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{ - Level: "bar", - } - - failHostDirPod := defaultPod() - failHostDirPod.Spec.Volumes = []api.Volume{ - { - Name: "bad volume", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{}, - }, - }, - } - - failHostPathDirPod := defaultPod() - failHostPathDirPod.Spec.Volumes = []api.Volume{ - { - Name: "bad volume", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/fail", - }, - }, - }, - } - failHostPathDirPSP := defaultPSP() - failHostPathDirPSP.Spec.Volumes = []policy.FSType{policy.HostPath} - failHostPathDirPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{ - {PathPrefix: "/foo/bar"}, - } - - failHostPathReadOnlyPod := defaultPod() - failHostPathReadOnlyPod.Spec.Containers[0].VolumeMounts = []api.VolumeMount{ - { - Name: "bad volume", - ReadOnly: false, - }, - } - failHostPathReadOnlyPod.Spec.Volumes = []api.Volume{ - { - Name: "bad volume", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - }, - }, - }, - } - failHostPathReadOnlyPSP := defaultPSP() - failHostPathReadOnlyPSP.Spec.Volumes = []policy.FSType{policy.HostPath} - failHostPathReadOnlyPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{ - { - PathPrefix: "/foo", - ReadOnly: true, - }, - } - - failSysctlDisallowedPSP := defaultPSP() - failSysctlDisallowedPSP.Spec.ForbiddenSysctls = []string{"kernel.shm_rmid_forced"} - - failNoSafeSysctlAllowedPSP := defaultPSP() - failNoSafeSysctlAllowedPSP.Spec.ForbiddenSysctls = []string{"*"} - - failAllUnsafeSysctlsPSP := defaultPSP() - failAllUnsafeSysctlsPSP.Spec.AllowedUnsafeSysctls = []string{} - - failSafeSysctlKernelPod := defaultPod() - failSafeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{ - Sysctls: []api.Sysctl{ - { - Name: "kernel.shm_rmid_forced", - Value: "1", - }, - }, - } - - failUnsafeSysctlPod := defaultPod() - failUnsafeSysctlPod.Spec.SecurityContext = &api.PodSecurityContext{ - Sysctls: []api.Sysctl{ - { - Name: "kernel.sem", - Value: "32000", - }, - }, - } - - failSeccompProfilePod := defaultPod() - failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"} - - podWithInvalidFlexVolumeDriver := defaultPod() - podWithInvalidFlexVolumeDriver.Spec.Volumes = []api.Volume{ - { - Name: "flex-volume", - VolumeSource: api.VolumeSource{ - FlexVolume: &api.FlexVolumeSource{ - Driver: "example/unknown", - }, - }, - }, - } - - failCSIDriverPod := defaultPod() - failCSIDriverPod.Spec.Volumes = []api.Volume{ - { - Name: "csi volume pod", - VolumeSource: api.VolumeSource{ - CSI: &api.CSIVolumeSource{ - Driver: "csi.driver.foo", - }, - }, - }, - } - - failGenericEphemeralPod := defaultPod() - failGenericEphemeralPod.Spec.Volumes = []api.Volume{ - { - Name: "generic ephemeral volume", - VolumeSource: api.VolumeSource{ - Ephemeral: &api.EphemeralVolumeSource{ - VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{}, - }, - }, - }, - } - - errorCases := map[string]struct { - pod *api.Pod - psp *policy.PodSecurityPolicy - expectedError string - }{ - "failHostNetwork": { - pod: failHostNetworkPod, - psp: defaultPSP(), - expectedError: "Host network is not allowed to be used", - }, - "failHostPID": { - pod: failHostPIDPod, - psp: defaultPSP(), - expectedError: "Host PID is not allowed to be used", - }, - "failHostIPC": { - pod: failHostIPCPod, - psp: defaultPSP(), - expectedError: "Host IPC is not allowed to be used", - }, - "failSupplementalGroupOutOfMustRange": { - pod: failSupplementalGroupPod, - psp: failSupplementalGroupMustPSP, - expectedError: "group 999 must be in the ranges: [{1 1}]", - }, - "failSupplementalGroupOutOfMayRange": { - pod: failSupplementalGroupPod, - psp: failSupplementalGroupMayPSP, - expectedError: "group 999 must be in the ranges: [{50 50} {55 998} {1000 1000}]", - }, - "failSupplementalGroupMustEmpty": { - pod: defaultPod(), - psp: failSupplementalGroupMustPSP, - expectedError: "unable to validate empty groups against required ranges", - }, - "failFSGroupOutOfMustRange": { - pod: failFSGroupPod, - psp: failFSGroupMustPSP, - expectedError: "group 999 must be in the ranges: [{1 1}]", - }, - "failFSGroupOutOfMayRange": { - pod: failFSGroupPod, - psp: failFSGroupMayPSP, - expectedError: "group 999 must be in the ranges: [{10 20} {1000 1001}]", - }, - "failFSGroupMustEmpty": { - pod: defaultPod(), - psp: failFSGroupMustPSP, - expectedError: "unable to validate empty groups against required ranges", - }, - "failNilSELinux": { - pod: failNilSELinuxPod, - psp: failSELinuxPSP, - expectedError: "seLinuxOptions: Required", - }, - "failInvalidSELinux": { - pod: failInvalidSELinuxPod, - psp: failSELinuxPSP, - expectedError: "seLinuxOptions.level: Invalid value", - }, - "failHostDirPSP": { - pod: failHostDirPod, - psp: defaultPSP(), - expectedError: "hostPath volumes are not allowed to be used", - }, - "failHostPathDirPSP": { - pod: failHostPathDirPod, - psp: failHostPathDirPSP, - expectedError: "is not allowed to be used", - }, - "failHostPathReadOnlyPSP": { - pod: failHostPathReadOnlyPod, - psp: failHostPathReadOnlyPSP, - expectedError: "must be read-only", - }, - "failSafeSysctlKernelPod with failNoSafeSysctlAllowedPSP": { - pod: failSafeSysctlKernelPod, - psp: failNoSafeSysctlAllowedPSP, - expectedError: "sysctl \"kernel.shm_rmid_forced\" is not allowed", - }, - "failSafeSysctlKernelPod with failSysctlDisallowedPSP": { - pod: failSafeSysctlKernelPod, - psp: failSysctlDisallowedPSP, - expectedError: "sysctl \"kernel.shm_rmid_forced\" is not allowed", - }, - "failUnsafeSysctlPod with failAllUnsafeSysctlsPSP": { - pod: failUnsafeSysctlPod, - psp: failAllUnsafeSysctlsPSP, - expectedError: "unsafe sysctl \"kernel.sem\" is not allowed", - }, - "failInvalidSeccomp": { - pod: failSeccompProfilePod, - psp: defaultPSP(), - expectedError: "Forbidden: seccomp may not be set", - }, - "fail pod with disallowed flexVolume when flex volumes are allowed": { - pod: podWithInvalidFlexVolumeDriver, - psp: allowFlexVolumesPSP(false, false), - expectedError: "Flexvolume driver is not allowed to be used", - }, - "fail pod with disallowed flexVolume when all volumes are allowed": { - pod: podWithInvalidFlexVolumeDriver, - psp: allowFlexVolumesPSP(false, true), - expectedError: "Flexvolume driver is not allowed to be used", - }, - "CSI policy using disallowed CDI driver": { - pod: failCSIDriverPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.CSI} - psp.Spec.AllowedCSIDrivers = []policy.AllowedCSIDriver{{Name: "csi.driver.disallowed"}} - return psp - }(), - expectedError: "Inline CSI driver is not allowed to be used", - }, - "Using inline CSI driver with no policy specified": { - pod: failCSIDriverPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.AllowedCSIDrivers = []policy.AllowedCSIDriver{{Name: "csi.driver.foo"}} - return psp - }(), - expectedError: "csi volumes are not allowed to be used", - }, - "policy.All using disallowed CDI driver": { - pod: failCSIDriverPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.All} - psp.Spec.AllowedCSIDrivers = []policy.AllowedCSIDriver{{Name: "csi.driver.disallowed"}} - return psp - }(), - expectedError: "Inline CSI driver is not allowed to be used", - }, - "CSI inline volumes without proper policy set": { - pod: failCSIDriverPod, - psp: defaultPSP(), - expectedError: "csi volumes are not allowed to be used", - }, - "generic ephemeral volumes without proper policy set": { - pod: failGenericEphemeralPod, - psp: defaultPSP(), - expectedError: "ephemeral volumes are not allowed to be used", - }, - "generic ephemeral volumes with other volume type allowed": { - pod: failGenericEphemeralPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.NFS} - return psp - }(), - expectedError: "ephemeral volumes are not allowed to be used", - }, - } - for name, test := range errorCases { - t.Run(name, func(t *testing.T) { - provider, err := NewSimpleProvider(test.psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "unable to create provider") - errs := provider.ValidatePod(test.pod) - require.NotEmpty(t, errs, "expected validation failure but did not receive errors") - assert.Contains(t, errs[0].Error(), test.expectedError, "received unexpected error") - }) - } -} - -func allowFlexVolumesPSP(allowAllFlexVolumes, allowAllVolumes bool) *policy.PodSecurityPolicy { - psp := defaultPSP() - - allowedVolumes := []policy.AllowedFlexVolume{ - {Driver: "example/foo"}, - {Driver: "example/bar"}, - } - if allowAllFlexVolumes { - allowedVolumes = []policy.AllowedFlexVolume{} - } - - allowedVolumeType := policy.FlexVolume - if allowAllVolumes { - allowedVolumeType = policy.All - } - - psp.Spec.AllowedFlexVolumes = allowedVolumes - psp.Spec.Volumes = []policy.FSType{allowedVolumeType} - - return psp -} - -func TestValidateContainerFailures(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)() - - // fail user strategy - failUserPSP := defaultPSP() - uid := int64(999) - badUID := int64(1) - failUserPSP.Spec.RunAsUser = policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyMustRunAs, - Ranges: []policy.IDRange{{Min: uid, Max: uid}}, - } - failUserPod := defaultPod() - failUserPod.Spec.Containers[0].SecurityContext.RunAsUser = &badUID - - // fail selinux strategy - failSELinuxPSP := defaultPSP() - failSELinuxPSP.Spec.SELinux = policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyMustRunAs, - SELinuxOptions: &v1.SELinuxOptions{ - Level: "foo", - }, - } - failSELinuxPod := defaultPod() - failSELinuxPod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{Level: "foo"} - failSELinuxPod.Spec.Containers[0].SecurityContext.SELinuxOptions = &api.SELinuxOptions{ - Level: "bar", - } - - failNilAppArmorPod := defaultPod() - v1FailInvalidAppArmorPod := defaultV1Pod() - apparmor.SetProfileName(v1FailInvalidAppArmorPod, defaultContainerName, v1.AppArmorBetaProfileNamePrefix+"foo") - failInvalidAppArmorPod := &api.Pod{} - k8s_api_v1.Convert_v1_Pod_To_core_Pod(v1FailInvalidAppArmorPod, failInvalidAppArmorPod, nil) - - failAppArmorPSP := defaultPSP() - failAppArmorPSP.Annotations = map[string]string{ - v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - } - - failPrivPod := defaultPod() - var priv = true - failPrivPod.Spec.Containers[0].SecurityContext.Privileged = &priv - - failProcMountPod := defaultPod() - failProcMountPod.Spec.Containers[0].SecurityContext.ProcMount = new(api.ProcMountType) - *failProcMountPod.Spec.Containers[0].SecurityContext.ProcMount = api.UnmaskedProcMount - - failCapsPod := defaultPod() - failCapsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{ - Add: []api.Capability{"foo"}, - } - - failHostPortPod := defaultPod() - failHostPortPod.Spec.Containers[0].Ports = []api.ContainerPort{{HostPort: 1}} - - readOnlyRootFSPSP := defaultPSP() - readOnlyRootFSPSP.Spec.ReadOnlyRootFilesystem = true - - readOnlyRootFSPodFalse := defaultPod() - 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 *policy.PodSecurityPolicy - expectedError string - }{ - "failUserPSP": { - pod: failUserPod, - psp: failUserPSP, - expectedError: "runAsUser: Invalid value", - }, - "failSELinuxPSP": { - pod: failSELinuxPod, - psp: failSELinuxPSP, - expectedError: "seLinuxOptions.level: Invalid value", - }, - "failNilAppArmor": { - pod: failNilAppArmorPod, - psp: failAppArmorPSP, - expectedError: "AppArmor profile must be set", - }, - "failInvalidAppArmor": { - pod: failInvalidAppArmorPod, - psp: failAppArmorPSP, - expectedError: "localhost/foo is not an allowed profile. Allowed values: \"runtime/default\"", - }, - "failPrivPSP": { - pod: failPrivPod, - psp: defaultPSP(), - expectedError: "Privileged containers are not allowed", - }, - "failProcMountPSP": { - pod: failProcMountPod, - psp: defaultPSP(), - expectedError: "ProcMountType is not allowed", - }, - "failCapsPSP": { - pod: failCapsPod, - psp: defaultPSP(), - expectedError: "capability may not be added", - }, - "failHostPortPSP": { - pod: failHostPortPod, - psp: defaultPSP(), - expectedError: "Host port 1 is not allowed to be used. Allowed ports: []", - }, - "failReadOnlyRootFS - nil": { - pod: defaultPod(), - psp: readOnlyRootFSPSP, - expectedError: "ReadOnlyRootFilesystem may not be nil and must be set to true", - }, - "failReadOnlyRootFS - false": { - pod: readOnlyRootFSPodFalse, - 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 name, test := range errorCases { - t.Run(name, func(t *testing.T) { - provider, err := NewSimpleProvider(test.psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "unable to create provider") - errs := provider.ValidatePod(test.pod) - require.NotEmpty(t, errs, "expected validation failure but did not receive errors") - assert.Contains(t, errs[0].Error(), test.expectedError, "unexpected error") - - // We want EphemeralContainers to behave the same as regular containers, so move the - // containers to ephemeralContainers and validate again. - ecPod := moveContainersToEphemeral(test.pod) - errs = provider.ValidatePod(ecPod) - require.NotEmpty(t, errs, "expected validation failure for ephemeral containers but did not receive errors") - assert.Contains(t, errs[0].Error(), test.expectedError, "unexpected error") - }) - } -} - -func TestValidatePodSuccess(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() - - hostNetworkPSP := defaultPSP() - hostNetworkPSP.Spec.HostNetwork = true - hostNetworkPod := defaultPod() - hostNetworkPod.Spec.SecurityContext.HostNetwork = true - - hostPIDPSP := defaultPSP() - hostPIDPSP.Spec.HostPID = true - hostPIDPod := defaultPod() - hostPIDPod.Spec.SecurityContext.HostPID = true - - hostIPCPSP := defaultPSP() - hostIPCPSP.Spec.HostIPC = true - hostIPCPod := defaultPod() - hostIPCPod.Spec.SecurityContext.HostIPC = true - - supGroupMustPSP := defaultPSP() - supGroupMustPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: 1, Max: 5}, - }, - } - supGroupMayPSP := defaultPSP() - supGroupMayPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyMayRunAs, - Ranges: []policy.IDRange{ - {Min: 1, Max: 5}, - }, - } - supGroupPod := defaultPod() - supGroupPod.Spec.SecurityContext.SupplementalGroups = []int64{3} - - fsGroupMustPSP := defaultPSP() - fsGroupMustPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: 1, Max: 5}, - }, - } - fsGroupMayPSP := defaultPSP() - fsGroupMayPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyMayRunAs, - Ranges: []policy.IDRange{ - {Min: 1, Max: 5}, - }, - } - fsGroupPod := defaultPod() - fsGroup := int64(3) - fsGroupPod.Spec.SecurityContext.FSGroup = &fsGroup - - seLinuxPod := defaultPod() - seLinuxPod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{ - User: "user", - Role: "role", - Type: "type", - Level: "level", - } - seLinuxPSP := defaultPSP() - seLinuxPSP.Spec.SELinux.Rule = policy.SELinuxStrategyMustRunAs - seLinuxPSP.Spec.SELinux.SELinuxOptions = &v1.SELinuxOptions{ - User: "user", - Role: "role", - Type: "type", - Level: "level", - } - - hostPathDirPodVolumeMounts := []api.VolumeMount{ - { - Name: "writeable /foo/bar", - ReadOnly: false, - }, - { - Name: "read only /foo/bar/baz", - ReadOnly: true, - }, - { - Name: "parent read only volume", - ReadOnly: true, - }, - { - Name: "read only child volume", - ReadOnly: true, - }, - } - - hostPathDirPod := defaultPod() - hostPathDirPod.Spec.InitContainers = []api.Container{ - { - Name: defaultContainerName, - VolumeMounts: hostPathDirPodVolumeMounts, - }, - } - - hostPathDirPod.Spec.Containers[0].VolumeMounts = hostPathDirPodVolumeMounts - hostPathDirPod.Spec.Volumes = []api.Volume{ - { - Name: "writeable /foo/bar", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo/bar", - }, - }, - }, - { - Name: "read only /foo/bar/baz", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo/bar/baz", - }, - }, - }, - { - Name: "parent read only volume", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo/", - }, - }, - }, - { - Name: "read only child volume", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo/readonly/child", - }, - }, - }, - } - - hostPathDirPSP := defaultPSP() - hostPathDirPSP.Spec.Volumes = []policy.FSType{policy.HostPath} - hostPathDirPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{ - // overlapping test case where child is different than parent directory. - {PathPrefix: "/foo/bar/baz", ReadOnly: true}, - {PathPrefix: "/foo", ReadOnly: true}, - {PathPrefix: "/foo/bar", ReadOnly: false}, - } - - hostPathDirAsterisksPSP := defaultPSP() - hostPathDirAsterisksPSP.Spec.Volumes = []policy.FSType{policy.All} - hostPathDirAsterisksPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{ - {PathPrefix: "/foo"}, - } - - sysctlAllowAllPSP := defaultPSP() - sysctlAllowAllPSP.Spec.ForbiddenSysctls = []string{} - sysctlAllowAllPSP.Spec.AllowedUnsafeSysctls = []string{"*"} - - safeSysctlKernelPod := defaultPod() - safeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{ - Sysctls: []api.Sysctl{ - { - Name: "kernel.shm_rmid_forced", - Value: "1", - }, - }, - } - - unsafeSysctlKernelPod := defaultPod() - unsafeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{ - Sysctls: []api.Sysctl{ - { - Name: "kernel.sem", - Value: "32000", - }, - }, - } - - seccompPSP := defaultPSP() - seccompPSP.Annotations = map[string]string{ - seccomp.AllowedProfilesAnnotationKey: "foo", - } - - seccompPod := defaultPod() - seccompPod.Annotations = map[string]string{ - api.SeccompPodAnnotationKey: "foo", - } - - flexVolumePod := defaultPod() - flexVolumePod.Spec.Volumes = []api.Volume{ - { - Name: "flex-volume", - VolumeSource: api.VolumeSource{ - FlexVolume: &api.FlexVolumeSource{ - Driver: "example/bar", - }, - }, - }, - } - - csiDriverPod := defaultPod() - csiDriverPod.Spec.Volumes = []api.Volume{ - { - Name: "csi inline driver", - VolumeSource: api.VolumeSource{ - CSI: &api.CSIVolumeSource{ - Driver: "foo", - }, - }, - }, - { - Name: "csi inline driver 2", - VolumeSource: api.VolumeSource{ - CSI: &api.CSIVolumeSource{ - Driver: "bar", - }, - }, - }, - { - Name: "csi inline driver 3", - VolumeSource: api.VolumeSource{ - CSI: &api.CSIVolumeSource{ - Driver: "baz", - }, - }, - }, - } - - genericEphemeralPod := defaultPod() - genericEphemeralPod.Spec.Volumes = []api.Volume{ - { - Name: "generic ephemeral volume", - VolumeSource: api.VolumeSource{ - Ephemeral: &api.EphemeralVolumeSource{ - VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{}, - }, - }, - }, - } - - successCases := map[string]struct { - pod *api.Pod - psp *policy.PodSecurityPolicy - }{ - "pass hostNetwork validating PSP": { - pod: hostNetworkPod, - psp: hostNetworkPSP, - }, - "pass hostPID validating PSP": { - pod: hostPIDPod, - psp: hostPIDPSP, - }, - "pass hostIPC validating PSP": { - pod: hostIPCPod, - psp: hostIPCPSP, - }, - "pass required supplemental group validating PSP": { - pod: supGroupPod, - psp: supGroupMustPSP, - }, - "pass optional supplemental group validation PSP": { - pod: supGroupPod, - psp: supGroupMayPSP, - }, - "pass optional supplemental group validation PSP - no pod group specified": { - pod: defaultPod(), - psp: supGroupMayPSP, - }, - "pass required fs group validating PSP": { - pod: fsGroupPod, - psp: fsGroupMustPSP, - }, - "pass optional fs group validating PSP": { - pod: fsGroupPod, - psp: fsGroupMayPSP, - }, - "pass optional fs group validating PSP - no pod group specified": { - pod: defaultPod(), - psp: fsGroupMayPSP, - }, - "pass selinux validating PSP": { - pod: seLinuxPod, - psp: seLinuxPSP, - }, - "pass sysctl specific profile with safe kernel sysctl": { - pod: safeSysctlKernelPod, - psp: sysctlAllowAllPSP, - }, - "pass sysctl specific profile with unsafe kernel sysctl": { - pod: unsafeSysctlKernelPod, - psp: sysctlAllowAllPSP, - }, - "pass hostDir allowed directory validating PSP": { - pod: hostPathDirPod, - psp: hostPathDirPSP, - }, - "pass hostDir all volumes allowed validating PSP": { - pod: hostPathDirPod, - psp: hostPathDirAsterisksPSP, - }, - "pass seccomp validating PSP": { - pod: seccompPod, - psp: seccompPSP, - }, - "flex volume driver in a allowlist (all volumes are allowed)": { - pod: flexVolumePod, - psp: allowFlexVolumesPSP(false, true), - }, - "flex volume driver with empty allowlist (all volumes are allowed)": { - pod: flexVolumePod, - psp: allowFlexVolumesPSP(true, true), - }, - "flex volume driver in a allowlist (only flex volumes are allowed)": { - pod: flexVolumePod, - psp: allowFlexVolumesPSP(false, false), - }, - "flex volume driver with empty allowlist (only flex volumes volumes are allowed)": { - pod: flexVolumePod, - psp: allowFlexVolumesPSP(true, false), - }, - "CSI policy with no CSI volumes used": { - pod: defaultPod(), - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.CSI} - psp.Spec.AllowedCSIDrivers = []policy.AllowedCSIDriver{{Name: "foo"}, {Name: "bar"}, {Name: "baz"}} - return psp - }(), - }, - "CSI policy with CSI inline volumes used": { - pod: csiDriverPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.CSI} - psp.Spec.AllowedCSIDrivers = []policy.AllowedCSIDriver{{Name: "foo"}, {Name: "bar"}, {Name: "baz"}} - return psp - }(), - }, - "policy.All with CSI inline volumes used": { - pod: csiDriverPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.All} - psp.Spec.AllowedCSIDrivers = []policy.AllowedCSIDriver{{Name: "foo"}, {Name: "bar"}, {Name: "baz"}} - return psp - }(), - }, - "generic ephemeral volume policy with generic ephemeral volume used": { - pod: genericEphemeralPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.Ephemeral} - return psp - }(), - }, - "policy.All with generic ephemeral volume used": { - pod: genericEphemeralPod, - psp: func() *policy.PodSecurityPolicy { - psp := defaultPSP() - psp.Spec.Volumes = []policy.FSType{policy.All} - return psp - }(), - }, - } - - for name, test := range successCases { - t.Run(name, func(t *testing.T) { - provider, err := NewSimpleProvider(test.psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "unable to create provider") - errs := provider.ValidatePod(test.pod) - assert.Empty(t, errs, "expected validation pass but received errors") - }) - } -} - -func TestValidateContainerSuccess(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)() - - // success user strategy - userPSP := defaultPSP() - uid := int64(999) - userPSP.Spec.RunAsUser = policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyMustRunAs, - Ranges: []policy.IDRange{{Min: uid, Max: uid}}, - } - userPod := defaultPod() - userPod.Spec.Containers[0].SecurityContext.RunAsUser = &uid - - // success selinux strategy - seLinuxPSP := defaultPSP() - seLinuxPSP.Spec.SELinux = policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyMustRunAs, - SELinuxOptions: &v1.SELinuxOptions{ - Level: "foo", - }, - } - seLinuxPod := defaultPod() - seLinuxPod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{Level: "foo"} - seLinuxPod.Spec.Containers[0].SecurityContext.SELinuxOptions = &api.SELinuxOptions{ - Level: "foo", - } - - appArmorPSP := defaultPSP() - appArmorPSP.Annotations = map[string]string{ - v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - } - v1AppArmorPod := defaultV1Pod() - apparmor.SetProfileName(v1AppArmorPod, defaultContainerName, v1.AppArmorBetaProfileRuntimeDefault) - appArmorPod := &api.Pod{} - k8s_api_v1.Convert_v1_Pod_To_core_Pod(v1AppArmorPod, appArmorPod, nil) - - privPSP := defaultPSP() - privPSP.Spec.Privileged = true - privPod := defaultPod() - var priv = true - privPod.Spec.Containers[0].SecurityContext.Privileged = &priv - - capsPSP := defaultPSP() - capsPSP.Spec.AllowedCapabilities = []v1.Capability{"foo"} - capsPod := defaultPod() - capsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{ - Add: []api.Capability{"foo"}, - } - - // pod should be able to request caps that are in the required set even if not specified in the allowed set - requiredCapsPSP := defaultPSP() - requiredCapsPSP.Spec.DefaultAddCapabilities = []v1.Capability{"foo"} - requiredCapsPod := defaultPod() - requiredCapsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{ - Add: []api.Capability{"foo"}, - } - - hostDirPSP := defaultPSP() - hostDirPSP.Spec.Volumes = []policy.FSType{policy.HostPath} - hostDirPod := defaultPod() - hostDirPod.Spec.Volumes = []api.Volume{ - { - Name: "bad volume", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{}, - }, - }, - } - - hostPortPSP := defaultPSP() - hostPortPSP.Spec.HostPorts = []policy.HostPortRange{{Min: 1, Max: 1}} - hostPortPod := defaultPod() - hostPortPod.Spec.Containers[0].Ports = []api.ContainerPort{{HostPort: 1}} - - readOnlyRootFSPodFalse := defaultPod() - readOnlyRootFSFalse := false - readOnlyRootFSPodFalse.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFSFalse - - readOnlyRootFSPodTrue := defaultPod() - 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.SeccompPodAnnotationKey: "foo", - api.SeccompContainerAnnotationKeyPrefix + seccompPod.Spec.Containers[0].Name: "foo", - } - - seccompPodInherit := defaultPod() - seccompPodInherit.Annotations = map[string]string{ - api.SeccompPodAnnotationKey: "foo", - } - - successCases := map[string]struct { - pod *api.Pod - psp *policy.PodSecurityPolicy - }{ - "pass user must run as PSP": { - pod: userPod, - psp: userPSP, - }, - "pass seLinux must run as PSP": { - pod: seLinuxPod, - psp: seLinuxPSP, - }, - "pass AppArmor allowed profiles": { - pod: appArmorPod, - psp: appArmorPSP, - }, - "pass priv validating PSP": { - pod: privPod, - psp: privPSP, - }, - "pass allowed caps validating PSP": { - pod: capsPod, - psp: capsPSP, - }, - "pass required caps validating PSP": { - pod: requiredCapsPod, - psp: requiredCapsPSP, - }, - "pass hostDir validating PSP": { - pod: hostDirPod, - psp: hostDirPSP, - }, - "pass hostPort validating PSP": { - pod: hostPortPod, - psp: hostPortPSP, - }, - "pass read only root fs - nil": { - pod: defaultPod(), - psp: defaultPSP(), - }, - "pass read only root fs - false": { - pod: readOnlyRootFSPodFalse, - psp: defaultPSP(), - }, - "pass read only root fs - true": { - pod: readOnlyRootFSPodTrue, - psp: defaultPSP(), - }, - "pass seccomp container annotation": { - pod: seccompPod, - psp: seccompPSP, - }, - "pass seccomp inherit pod annotation": { - pod: seccompPodInherit, - psp: seccompPSP, - }, - } - - for name, test := range successCases { - t.Run(name, func(t *testing.T) { - provider, err := NewSimpleProvider(test.psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "unable to create provider") - errs := provider.ValidatePod(test.pod) - assert.Empty(t, errs, "expected validation pass but received errors") - - // We want EphemeralContainers to behave the same as regular containers, so move the - // containers to ephemeralContainers and validate again. - ecPod := moveContainersToEphemeral(test.pod) - errs = provider.ValidatePod(ecPod) - assert.Empty(t, errs, "expected validation pass for ephemeral containers but received errors") - }) - } -} - -func TestGenerateContainerSecurityContextReadOnlyRootFS(t *testing.T) { - truePSP := defaultPSP() - truePSP.Spec.ReadOnlyRootFilesystem = true - - trueVal := true - expectTrue := &trueVal - falseVal := false - expectFalse := &falseVal - - falsePod := defaultPod() - falsePod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = expectFalse - - truePod := defaultPod() - truePod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = expectTrue - - tests := map[string]struct { - pod *api.Pod - psp *policy.PodSecurityPolicy - expected *bool - }{ - "false psp, nil sc": { - psp: defaultPSP(), - pod: defaultPod(), - expected: nil, - }, - "false psp, false sc": { - psp: defaultPSP(), - pod: falsePod, - expected: expectFalse, - }, - "false psp, true sc": { - psp: defaultPSP(), - pod: truePod, - expected: expectTrue, - }, - "true psp, nil sc": { - psp: truePSP, - pod: defaultPod(), - expected: expectTrue, - }, - "true psp, false sc": { - psp: truePSP, - pod: falsePod, - // expect false even though it defaults to true to ensure it doesn't change set values - // validation catches the mismatch, not generation - expected: expectFalse, - }, - "true psp, true sc": { - psp: truePSP, - pod: truePod, - expected: expectTrue, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - provider, err := NewSimpleProvider(test.psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "unable to create provider") - err = provider.MutatePod(test.pod) - require.NoError(t, err, "unable to mutate container") - - sc := test.pod.Spec.Containers[0].SecurityContext - if test.expected == nil { - assert.Nil(t, sc.ReadOnlyRootFilesystem, "expected a nil ReadOnlyRootFilesystem") - } else { - require.NotNil(t, sc.ReadOnlyRootFilesystem, "expected a non nil ReadOnlyRootFilesystem") - assert.Equal(t, *test.expected, *sc.ReadOnlyRootFilesystem) - } - }) - } -} - -func defaultPSP() *policy.PodSecurityPolicy { - return defaultNamedPSP("psp-sa") -} - -func defaultNamedPSP(name string) *policy.PodSecurityPolicy { - allowPrivilegeEscalation := true - return &policy.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{}, - }, - Spec: policy.PodSecurityPolicySpec{ - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyRunAsAny, - }, - RunAsGroup: &policy.RunAsGroupStrategyOptions{ - Rule: policy.RunAsGroupStrategyRunAsAny, - }, - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyRunAsAny, - }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyRunAsAny, - }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyRunAsAny, - }, - AllowPrivilegeEscalation: &allowPrivilegeEscalation, - }, - } -} - -func defaultPod() *api.Pod { - var notPriv = false - return &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{}, - }, - Spec: api.PodSpec{ - SecurityContext: &api.PodSecurityContext{ - // fill in for test cases - }, - Containers: []api.Container{ - { - Name: defaultContainerName, - SecurityContext: &api.SecurityContext{ - // expected to be set by defaulting mechanisms - Privileged: ¬Priv, - // fill in the rest for test cases - }, - }, - }, - }, - } -} - -func defaultV1Pod() *v1.Pod { - var notPriv = false - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{}, - }, - Spec: v1.PodSpec{ - SecurityContext: &v1.PodSecurityContext{ - // fill in for test cases - }, - Containers: []v1.Container{ - { - Name: defaultContainerName, - SecurityContext: &v1.SecurityContext{ - // expected to be set by defaulting mechanisms - Privileged: ¬Priv, - // fill in the rest for test cases - }, - }, - }, - }, - } -} - -func moveContainersToEphemeral(in *api.Pod) *api.Pod { - out := in.DeepCopy() - for _, c := range out.Spec.Containers { - out.Spec.EphemeralContainers = append(out.Spec.EphemeralContainers, api.EphemeralContainer{ - EphemeralContainerCommon: api.EphemeralContainerCommon(c), - }) - } - out.Spec.Containers = nil - return out -} - -// TestValidateAllowedVolumes will test that for every field of VolumeSource we can create -// a pod with that type of volume and deny it, accept it explicitly, or accept it with -// the FSTypeAll wildcard. -func TestValidateAllowedVolumes(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() - - val := reflect.ValueOf(api.VolumeSource{}) - - for i := 0; i < val.NumField(); i++ { - // reflectively create the volume source - fieldVal := val.Type().Field(i) - - t.Run(fieldVal.Name, func(t *testing.T) { - volumeSource := api.VolumeSource{} - volumeSourceVolume := reflect.New(fieldVal.Type.Elem()) - - reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume) - volume := api.Volume{VolumeSource: volumeSource} - - // sanity check before moving on - fsType, err := psputil.GetVolumeFSType(volume) - require.NoError(t, err, "error getting FSType") - - // add the volume to the pod - pod := defaultPod() - pod.Spec.Volumes = []api.Volume{volume} - - // create a PSP that allows no volumes - psp := defaultPSP() - - provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "error creating provider") - - // expect a denial for this PSP and test the error message to ensure it's related to the volumesource - errs := provider.ValidatePod(pod) - require.Len(t, errs, 1, "expected exactly 1 error") - assert.Contains(t, errs.ToAggregate().Error(), fmt.Sprintf("%s volumes are not allowed to be used", fsType), "did not find the expected error") - - // now add the fstype directly to the psp and it should validate - psp.Spec.Volumes = []policy.FSType{fsType} - errs = provider.ValidatePod(pod) - assert.Empty(t, errs, "directly allowing volume expected no errors") - - // now change the psp to allow any volumes and the pod should still validate - psp.Spec.Volumes = []policy.FSType{policy.All} - errs = provider.ValidatePod(pod) - assert.Empty(t, errs, "wildcard volume expected no errors") - }) - } -} - -func TestValidateProjectedVolume(t *testing.T) { - pod := defaultPod() - psp := defaultPSP() - provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "error creating provider") - - tests := []struct { - desc string - allowedFSTypes []policy.FSType - projectedVolumeSource *api.ProjectedVolumeSource - wantAllow bool - }{ - { - desc: "deny if secret is not allowed", - allowedFSTypes: []policy.FSType{policy.EmptyDir}, - projectedVolumeSource: serviceaccount.TokenVolumeSource(), - wantAllow: false, - }, - { - desc: "deny if the projected volume has volume source other than the ones in projected volume injected by service account token admission plugin", - allowedFSTypes: []policy.FSType{policy.Secret}, - projectedVolumeSource: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - { - ConfigMap: &api.ConfigMapProjection{ - LocalObjectReference: api.LocalObjectReference{ - Name: "foo-ca.crt", - }, - Items: []api.KeyToPath{ - { - Key: "ca.crt", - Path: "ca.crt", - }, - }, - }, - }, - }}, - wantAllow: false, - }, - { - desc: "allow if secret is allowed and the projected volume sources equals to the ones injected by service account admission plugin", - allowedFSTypes: []policy.FSType{policy.Secret}, - projectedVolumeSource: serviceaccount.TokenVolumeSource(), - wantAllow: true, - }, - } - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - pod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{Projected: test.projectedVolumeSource}}} - psp.Spec.Volumes = test.allowedFSTypes - errs := provider.ValidatePod(pod) - if test.wantAllow { - assert.Empty(t, errs, "projected volumes are allowed") - } else { - assert.Contains(t, errs.ToAggregate().Error(), fmt.Sprintf("projected volumes are not allowed to be used"), "did not find the expected error") - } - }) - } -} - -func TestAllowPrivilegeEscalation(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)() - - ptr := pointer.BoolPtr - tests := []struct { - pspAPE bool // PSP AllowPrivilegeEscalation - pspDAPE *bool // PSP DefaultAllowPrivilegeEscalation - podAPE *bool // Pod AllowPrivilegeEscalation - expectErr bool - expectAPE *bool // Expected value of pod APE (if no error) - }{ - // Test all valid combinations of PSP AllowPrivilegeEscalation, - // DefaultAllowPrivilegeEscalation, and Pod AllowPrivilegeEscalation. - {true, nil, nil, false, nil}, - {true, nil, ptr(false), false, ptr(false)}, - {true, nil, ptr(true), false, ptr(true)}, - {true, ptr(false), nil, false, ptr(false)}, - {true, ptr(false), ptr(false), false, ptr(false)}, - {true, ptr(false), ptr(true), false, ptr(true)}, - {true, ptr(true), nil, false, ptr(true)}, - {true, ptr(true), ptr(false), false, ptr(false)}, - {true, ptr(true), ptr(true), false, ptr(true)}, - {false, nil, nil, false, ptr(false)}, - {false, nil, ptr(false), false, ptr(false)}, - {false, nil, ptr(true), true, nil}, - {false, ptr(false), nil, false, ptr(false)}, - {false, ptr(false), ptr(false), false, ptr(false)}, - {false, ptr(false), ptr(true), true, nil}, - // Invalid cases: pspAPE=false, pspDAPE=true - } - - fmtPtr := func(b *bool) string { - if b == nil { - return "nil" - } - return strconv.FormatBool(*b) - } - for _, test := range tests { - t.Run(fmt.Sprintf("pspAPE:%t_pspDAPE:%s_podAPE:%s", test.pspAPE, fmtPtr(test.pspDAPE), fmtPtr(test.podAPE)), func(t *testing.T) { - pod := defaultPod() - pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = test.podAPE - ecPod := moveContainersToEphemeral(pod) - - psp := defaultPSP() - psp.Spec.AllowPrivilegeEscalation = &test.pspAPE - psp.Spec.DefaultAllowPrivilegeEscalation = test.pspDAPE - - provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err) - - err = provider.MutatePod(pod) - require.NoError(t, err) - - errs := provider.ValidatePod(pod) - if test.expectErr { - assert.NotEmpty(t, errs, "expected validation error") - } else { - assert.Empty(t, errs, "expected no validation errors") - ape := pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation - assert.Equal(t, test.expectAPE, ape, "expected pod AllowPrivilegeEscalation") - } - - err = provider.MutatePod(ecPod) - require.NoError(t, err) - - errs = provider.ValidatePod(ecPod) - if test.expectErr { - assert.NotEmpty(t, errs, "expected validation error for ephemeral containers") - } else { - assert.Empty(t, errs, "expected no validation errors for ephemeral containers") - ape := ecPod.Spec.EphemeralContainers[0].SecurityContext.AllowPrivilegeEscalation - assert.Equal(t, test.expectAPE, ape, "expected pod AllowPrivilegeEscalation for ephemeral container") - } - }) - } -} - -func TestDefaultRuntimeClassName(t *testing.T) { - const ( - defaultedName = "foo" - presetName = "tim" - ) - - noRCS := defaultNamedPSP("nil-strategy") - emptyRCS := defaultNamedPSP("empty-strategy") - emptyRCS.Spec.RuntimeClass = &policy.RuntimeClassStrategyOptions{} - noDefaultRCS := defaultNamedPSP("no-default") - noDefaultRCS.Spec.RuntimeClass = &policy.RuntimeClassStrategyOptions{ - AllowedRuntimeClassNames: []string{"foo", "bar"}, - } - defaultRCS := defaultNamedPSP("defaulting") - defaultRCS.Spec.RuntimeClass = &policy.RuntimeClassStrategyOptions{ - DefaultRuntimeClassName: pointer.StringPtr(defaultedName), - } - - noRCPod := defaultPod() - noRCPod.Name = "no-runtimeclass" - rcPod := defaultPod() - rcPod.Name = "preset-runtimeclass" - rcPod.Spec.RuntimeClassName = pointer.StringPtr(presetName) - - type testcase struct { - psp *policy.PodSecurityPolicy - pod *api.Pod - expectedRuntimeClassName *string - } - tests := []testcase{{ - psp: defaultRCS, - pod: noRCPod, - expectedRuntimeClassName: pointer.StringPtr(defaultedName), - }} - // Non-defaulting no-preset cases - for _, psp := range []*policy.PodSecurityPolicy{noRCS, emptyRCS, noDefaultRCS} { - tests = append(tests, testcase{psp, noRCPod, nil}) - } - // Non-defaulting preset cases - for _, psp := range []*policy.PodSecurityPolicy{noRCS, emptyRCS, noDefaultRCS, defaultRCS} { - tests = append(tests, testcase{psp, rcPod, pointer.StringPtr(presetName)}) - } - - for _, test := range tests { - t.Run(fmt.Sprintf("%s-psp %s-pod", test.psp.Name, test.pod.Name), func(t *testing.T) { - provider, err := NewSimpleProvider(test.psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "error creating provider") - - actualPod := test.pod.DeepCopy() - require.NoError(t, provider.MutatePod(actualPod)) - - expectedPod := test.pod.DeepCopy() - expectedPod.Spec.RuntimeClassName = test.expectedRuntimeClassName - assert.Equal(t, expectedPod, actualPod) - }) - } -} - -func TestAllowedRuntimeClassNames(t *testing.T) { - const ( - goodName = "good" - ) - - noRCPod := defaultPod() - noRCPod.Name = "no-runtimeclass" - rcPod := defaultPod() - rcPod.Name = "good-runtimeclass" - rcPod.Spec.RuntimeClassName = pointer.StringPtr(goodName) - otherPod := defaultPod() - otherPod.Name = "bad-runtimeclass" - otherPod.Spec.RuntimeClassName = pointer.StringPtr("bad") - allPods := []*api.Pod{noRCPod, rcPod, otherPod} - - type testcase struct { - name string - strategy *policy.RuntimeClassStrategyOptions - validPods []*api.Pod - invalidPods []*api.Pod - } - tests := []testcase{{ - name: "nil-strategy", - validPods: allPods, - }, { - name: "empty-strategy", - strategy: &policy.RuntimeClassStrategyOptions{ - AllowedRuntimeClassNames: []string{}, - }, - validPods: []*api.Pod{noRCPod}, - invalidPods: []*api.Pod{rcPod, otherPod}, - }, { - name: "allow-all-strategy", - strategy: &policy.RuntimeClassStrategyOptions{ - AllowedRuntimeClassNames: []string{"*"}, - DefaultRuntimeClassName: pointer.StringPtr("foo"), - }, - validPods: allPods, - }, { - name: "named-allowed", - strategy: &policy.RuntimeClassStrategyOptions{ - AllowedRuntimeClassNames: []string{goodName}, - }, - validPods: []*api.Pod{rcPod}, - invalidPods: []*api.Pod{otherPod}, - }} - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - psp := defaultNamedPSP(test.name) - psp.Spec.RuntimeClass = test.strategy - provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) - require.NoError(t, err, "error creating provider") - - for _, pod := range test.validPods { - copy := pod.DeepCopy() - assert.NoError(t, provider.ValidatePod(copy).ToAggregate(), "expected valid pod %s", pod.Name) - assert.Equal(t, pod, copy, "validate should not mutate!") - } - for _, pod := range test.invalidPods { - copy := pod.DeepCopy() - assert.Error(t, provider.ValidatePod(copy).ToAggregate(), "expected invalid pod %s", pod.Name) - assert.Equal(t, pod, copy, "validate should not mutate!") - } - }) - } -} diff --git a/pkg/security/podsecuritypolicy/seccomp/strategy.go b/pkg/security/podsecuritypolicy/seccomp/strategy.go deleted file mode 100644 index a9cfa421611..00000000000 --- a/pkg/security/podsecuritypolicy/seccomp/strategy.go +++ /dev/null @@ -1,178 +0,0 @@ -/* -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" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/validation/field" - podutil "k8s.io/kubernetes/pkg/api/pod" - api "k8s.io/kubernetes/pkg/apis/core" -) - -const ( - // AllowAny is the wildcard used to allow any profile. - AllowAny = "*" - // DefaultProfileAnnotationKey specifies the default seccomp profile. - DefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName" - // AllowedProfilesAnnotationKey specifies 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 - } - // With the graduation of seccomp to GA we automatically convert - // the deprecated seccomp profile annotation `docker/default` to - // `runtime/default`. This means that we now have to automatically - // allow `runtime/default` if a user specifies `docker/default` and - // vice versa in a PSP. - if p == v1.DeprecatedSeccompProfileDockerDefault || p == v1.SeccompProfileRuntimeDefault { - allowedProfiles[v1.SeccompProfileRuntimeDefault] = true - allowedProfiles[v1.DeprecatedSeccompProfileDockerDefault] = true - } - 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 - } - if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { - // Profile field already set, translate to annotation - return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile), 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 the annotation is not set, see if the field is set and derive the corresponding annotation value - if len(podProfile) == 0 && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { - podProfile = podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile) - } - - 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 { - if container.SecurityContext != nil && container.SecurityContext.SeccompProfile != nil { - // derive the annotation value from the container field - return podutil.SeccompAnnotationForField(container.SecurityContext.SeccompProfile) - } - containerProfile, ok := pod.Annotations[api.SeccompContainerAnnotationKeyPrefix+container.Name] - if ok { - // return the existing container annotation - return containerProfile - } - if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { - // derive the annotation value from the pod field - return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile) - } - // return the existing pod annotation - return pod.Annotations[api.SeccompPodAnnotationKey] -} diff --git a/pkg/security/podsecuritypolicy/seccomp/strategy_test.go b/pkg/security/podsecuritypolicy/seccomp/strategy_test.go deleted file mode 100644 index 1f93ee3593e..00000000000 --- a/pkg/security/podsecuritypolicy/seccomp/strategy_test.go +++ /dev/null @@ -1,419 +0,0 @@ -/* -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" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - api "k8s.io/kubernetes/pkg/apis/core" -) - -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", - } - allowSpecificLocalhost = map[string]string{ - AllowedProfilesAnnotationKey: v1.SeccompLocalhostProfileNamePrefix + "foo", - } - allowSpecificDockerDefault = map[string]string{ - AllowedProfilesAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault, - } - allowSpecificRuntimeDefault = map[string]string{ - AllowedProfilesAnnotationKey: v1.SeccompProfileRuntimeDefault, - } -) - -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 { - s := NewStrategy(v.annotations) - internalStrat, _ := s.(*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) { - bar := "bar" - tests := map[string]struct { - pspAnnotations map[string]string - podAnnotations map[string]string - seccompProfile *api.SeccompProfile - 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", - }, - "seccomp with default, pod field": { - pspAnnotations: allowAnyDefault, - seccompProfile: &api.SeccompProfile{ - Type: api.SeccompProfileTypeLocalhost, - LocalhostProfile: &bar, - }, - expectedProfile: "localhost/bar", - }, - } - for k, v := range tests { - s := NewStrategy(v.pspAnnotations) - actual, err := s.Generate(v.podAnnotations, &api.Pod{ - Spec: api.PodSpec{ - SecurityContext: &api.PodSecurityContext{ - SeccompProfile: v.seccompProfile, - }, - }, - }) - - 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) { - foo := "foo" - tests := map[string]struct { - pspAnnotations map[string]string - podAnnotations map[string]string - seccompProfile *api.SeccompProfile - 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: "", - }, - "valid pod annotations and field, required profiles": { - pspAnnotations: allowSpecific, - podAnnotations: map[string]string{ - api.SeccompPodAnnotationKey: "foo", - }, - seccompProfile: &api.SeccompProfile{ - Type: api.SeccompProfileTypeLocalhost, - LocalhostProfile: &foo, - }, - expectedError: "", - }, - "valid pod field and no annotation, required profiles": { - pspAnnotations: allowSpecific, - seccompProfile: &api.SeccompProfile{ - Type: api.SeccompProfileTypeLocalhost, - LocalhostProfile: &foo, - }, - expectedError: "Forbidden: localhost/foo is not an allowed seccomp profile. Valid values are foo", - }, - "valid pod field and no annotation, required profiles (localhost)": { - pspAnnotations: allowSpecificLocalhost, - seccompProfile: &api.SeccompProfile{ - Type: api.SeccompProfileTypeLocalhost, - LocalhostProfile: &foo, - }, - expectedError: "", - }, - "docker/default PSP annotation automatically allows runtime/default pods": { - pspAnnotations: allowSpecificDockerDefault, - podAnnotations: map[string]string{ - api.SeccompPodAnnotationKey: v1.SeccompProfileRuntimeDefault, - }, - expectedError: "", - }, - "runtime/default PSP annotation automatically allows docker/default pods": { - pspAnnotations: allowSpecificRuntimeDefault, - podAnnotations: map[string]string{ - api.SeccompPodAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault, - }, - expectedError: "", - }, - } - for k, v := range tests { - pod := &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: v.podAnnotations, - }, - Spec: api.PodSpec{ - SecurityContext: &api.PodSecurityContext{ - SeccompProfile: v.seccompProfile, - }, - }, - } - s := NewStrategy(v.pspAnnotations) - errs := s.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) { - foo := "foo" - bar := "bar" - tests := map[string]struct { - pspAnnotations map[string]string - podAnnotations map[string]string - seccompProfile *api.SeccompProfile - 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", - }, - "valid container field and no annotation, required profiles": { - pspAnnotations: allowSpecificLocalhost, - seccompProfile: &api.SeccompProfile{ - Type: api.SeccompProfileTypeLocalhost, - LocalhostProfile: &foo, - }, - expectedError: "", - }, - "invalid container field and no annotation, required profiles": { - pspAnnotations: allowSpecificLocalhost, - seccompProfile: &api.SeccompProfile{ - Type: api.SeccompProfileTypeLocalhost, - LocalhostProfile: &bar, - }, - expectedError: "Forbidden: localhost/bar is not an allowed seccomp profile. Valid values are localhost/foo", - }, - } - for k, v := range tests { - pod := &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: v.podAnnotations, - }, - } - container := &api.Container{ - Name: "container", - SecurityContext: &api.SecurityContext{ - SeccompProfile: v.seccompProfile, - }, - } - - s := NewStrategy(v.pspAnnotations) - errs := s.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()) - } - } -} diff --git a/pkg/security/podsecuritypolicy/selinux/doc.go b/pkg/security/podsecuritypolicy/selinux/doc.go deleted file mode 100644 index 506981a9f40..00000000000 --- a/pkg/security/podsecuritypolicy/selinux/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -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 selinux contains code for validating and defaulting the SELinux -// context of a pod according to a security policy. -package selinux diff --git a/pkg/security/podsecuritypolicy/selinux/mustrunas.go b/pkg/security/podsecuritypolicy/selinux/mustrunas.go deleted file mode 100644 index 11b881e09cb..00000000000 --- a/pkg/security/podsecuritypolicy/selinux/mustrunas.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -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 selinux - -import ( - "fmt" - "sort" - "strings" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/core/v1" - "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" -) - -type mustRunAs struct { - opts *api.SELinuxOptions -} - -var _ SELinuxStrategy = &mustRunAs{} - -func NewMustRunAs(options *policy.SELinuxStrategyOptions) (SELinuxStrategy, error) { - if options == nil { - return nil, fmt.Errorf("MustRunAs requires SELinuxContextStrategyOptions") - } - if options.SELinuxOptions == nil { - return nil, fmt.Errorf("MustRunAs requires SELinuxOptions") - } - - internalSELinuxOptions := &api.SELinuxOptions{} - if err := v1.Convert_v1_SELinuxOptions_To_core_SELinuxOptions(options.SELinuxOptions, internalSELinuxOptions, nil); err != nil { - return nil, err - } - return &mustRunAs{ - opts: internalSELinuxOptions, - }, nil -} - -// Generate creates the SELinuxOptions based on constraint rules. -func (s *mustRunAs) Generate(_ *api.Pod, _ *api.Container) (*api.SELinuxOptions, error) { - return s.opts, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, seLinux *api.SELinuxOptions) field.ErrorList { - allErrs := field.ErrorList{} - - if seLinux == nil { - allErrs = append(allErrs, field.Required(fldPath, "")) - return allErrs - } - if !equalLevels(s.opts.Level, seLinux.Level) { - detail := fmt.Sprintf("must be %s", s.opts.Level) - allErrs = append(allErrs, field.Invalid(fldPath.Child("level"), seLinux.Level, detail)) - } - if seLinux.Role != s.opts.Role { - detail := fmt.Sprintf("must be %s", s.opts.Role) - allErrs = append(allErrs, field.Invalid(fldPath.Child("role"), seLinux.Role, detail)) - } - if seLinux.Type != s.opts.Type { - detail := fmt.Sprintf("must be %s", s.opts.Type) - allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), seLinux.Type, detail)) - } - if seLinux.User != s.opts.User { - detail := fmt.Sprintf("must be %s", s.opts.User) - allErrs = append(allErrs, field.Invalid(fldPath.Child("user"), seLinux.User, detail)) - } - - return allErrs -} - -// equalLevels compares SELinux levels for equality. -func equalLevels(expected, actual string) bool { - if expected == actual { - return true - } - // "s0:c6,c0" => [ "s0", "c6,c0" ] - expectedParts := strings.SplitN(expected, ":", 2) - actualParts := strings.SplitN(actual, ":", 2) - - // both SELinux levels must be in a format "sX:cY" - if len(expectedParts) != 2 || len(actualParts) != 2 { - return false - } - - if !equalSensitivity(expectedParts[0], actualParts[0]) { - return false - } - - if !equalCategories(expectedParts[1], actualParts[1]) { - return false - } - - return true -} - -// equalSensitivity compares sensitivities of the SELinux levels for equality. -func equalSensitivity(expected, actual string) bool { - return expected == actual -} - -// equalCategories compares categories of the SELinux levels for equality. -func equalCategories(expected, actual string) bool { - expectedCategories := strings.Split(expected, ",") - actualCategories := strings.Split(actual, ",") - - sort.Strings(expectedCategories) - sort.Strings(actualCategories) - - return util.EqualStringSlices(expectedCategories, actualCategories) -} diff --git a/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go b/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go deleted file mode 100644 index b386d8a0498..00000000000 --- a/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go +++ /dev/null @@ -1,183 +0,0 @@ -/* -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 selinux - -import ( - corev1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/core/v1" - "reflect" - "strings" - "testing" -) - -func TestMustRunAsOptions(t *testing.T) { - tests := map[string]struct { - opts *policy.SELinuxStrategyOptions - pass bool - }{ - "nil opts": { - opts: nil, - pass: false, - }, - "invalid opts": { - opts: &policy.SELinuxStrategyOptions{}, - pass: false, - }, - "valid opts": { - opts: &policy.SELinuxStrategyOptions{SELinuxOptions: &corev1.SELinuxOptions{}}, - pass: true, - }, - } - for name, tc := range tests { - _, err := NewMustRunAs(tc.opts) - if err != nil && tc.pass { - t.Errorf("%s expected to pass but received error %#v", name, err) - } - if err == nil && !tc.pass { - t.Errorf("%s expected to fail but did not receive an error", name) - } - } -} - -func TestMustRunAsGenerate(t *testing.T) { - opts := &policy.SELinuxStrategyOptions{ - SELinuxOptions: &corev1.SELinuxOptions{ - User: "user", - Role: "role", - Type: "type", - Level: "level", - }, - } - mustRunAs, err := NewMustRunAs(opts) - if err != nil { - t.Fatalf("unexpected error initializing NewMustRunAs %v", err) - } - generated, err := mustRunAs.Generate(nil, nil) - if err != nil { - t.Fatalf("unexpected error generating selinux %v", err) - } - internalSELinuxOptions := &api.SELinuxOptions{} - v1.Convert_v1_SELinuxOptions_To_core_SELinuxOptions(opts.SELinuxOptions, internalSELinuxOptions, nil) - if !reflect.DeepEqual(generated, internalSELinuxOptions) { - t.Errorf("generated selinux does not equal configured selinux") - } -} - -func TestMustRunAsValidate(t *testing.T) { - newValidOpts := func() *corev1.SELinuxOptions { - return &corev1.SELinuxOptions{ - User: "user", - Role: "role", - Level: "s0:c0,c6", - Type: "type", - } - } - - newValidOptsWithLevel := func(level string) *corev1.SELinuxOptions { - opts := newValidOpts() - opts.Level = level - return opts - } - - role := newValidOpts() - role.Role = "invalid" - - user := newValidOpts() - user.User = "invalid" - - seType := newValidOpts() - seType.Type = "invalid" - - validOpts := newValidOpts() - - tests := map[string]struct { - podSeLinux *corev1.SELinuxOptions - pspSeLinux *corev1.SELinuxOptions - expectedMsg string - }{ - "invalid role": { - podSeLinux: role, - pspSeLinux: validOpts, - expectedMsg: "role: Invalid value", - }, - "invalid user": { - podSeLinux: user, - pspSeLinux: validOpts, - expectedMsg: "user: Invalid value", - }, - "levels are not equal": { - podSeLinux: newValidOptsWithLevel("s0"), - pspSeLinux: newValidOptsWithLevel("s0:c1,c2"), - expectedMsg: "level: Invalid value", - }, - "levels differ by sensitivity": { - podSeLinux: newValidOptsWithLevel("s0:c6"), - pspSeLinux: newValidOptsWithLevel("s1:c6"), - expectedMsg: "level: Invalid value", - }, - "levels differ by categories": { - podSeLinux: newValidOptsWithLevel("s0:c0,c8"), - pspSeLinux: newValidOptsWithLevel("s0:c1,c7"), - expectedMsg: "level: Invalid value", - }, - "valid": { - podSeLinux: validOpts, - pspSeLinux: validOpts, - expectedMsg: "", - }, - "valid with different order of categories": { - podSeLinux: newValidOptsWithLevel("s0:c6,c0"), - pspSeLinux: validOpts, - expectedMsg: "", - }, - } - - for name, tc := range tests { - opts := &policy.SELinuxStrategyOptions{ - SELinuxOptions: tc.pspSeLinux, - } - mustRunAs, err := NewMustRunAs(opts) - if err != nil { - t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err) - continue - } - - internalSELinuxOptions := api.SELinuxOptions{} - v1.Convert_v1_SELinuxOptions_To_core_SELinuxOptions(tc.podSeLinux, &internalSELinuxOptions, nil) - errs := mustRunAs.Validate(nil, nil, nil, &internalSELinuxOptions) - //should've passed but didn't - if len(tc.expectedMsg) == 0 && len(errs) > 0 { - t.Errorf("%s expected no errors but received %v", name, errs) - } - //should've failed but didn't - if len(tc.expectedMsg) != 0 && len(errs) == 0 { - t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg) - } - //failed with additional messages - if len(tc.expectedMsg) != 0 && len(errs) > 1 { - t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs) - } - //check that we got the right message - if len(tc.expectedMsg) != 0 && len(errs) == 1 { - if !strings.Contains(errs[0].Error(), tc.expectedMsg) { - t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs) - } - } - } -} diff --git a/pkg/security/podsecuritypolicy/selinux/runasany.go b/pkg/security/podsecuritypolicy/selinux/runasany.go deleted file mode 100644 index b225b090432..00000000000 --- a/pkg/security/podsecuritypolicy/selinux/runasany.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -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 selinux - -import ( - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// runAsAny implements the SELinuxStrategy interface. -type runAsAny struct{} - -var _ SELinuxStrategy = &runAsAny{} - -// NewRunAsAny provides a strategy that will return the configured se linux context or nil. -func NewRunAsAny(options *policy.SELinuxStrategyOptions) (SELinuxStrategy, error) { - return &runAsAny{}, nil -} - -// Generate creates the SELinuxOptions based on constraint rules. -func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) { - return nil, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, options *api.SELinuxOptions) field.ErrorList { - return field.ErrorList{} -} diff --git a/pkg/security/podsecuritypolicy/selinux/runasany_test.go b/pkg/security/podsecuritypolicy/selinux/runasany_test.go deleted file mode 100644 index cebb83477a8..00000000000 --- a/pkg/security/podsecuritypolicy/selinux/runasany_test.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -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 selinux - -import ( - corev1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - "testing" -) - -func TestRunAsAnyOptions(t *testing.T) { - _, err := NewRunAsAny(nil) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - _, err = NewRunAsAny(&policy.SELinuxStrategyOptions{}) - if err != nil { - t.Errorf("unexpected error initializing NewRunAsAny %v", err) - } -} - -func TestRunAsAnyGenerate(t *testing.T) { - s, err := NewRunAsAny(&policy.SELinuxStrategyOptions{}) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - uid, err := s.Generate(nil, nil) - if uid != nil { - t.Errorf("expected nil uid but got %v", *uid) - } - if err != nil { - t.Errorf("unexpected error generating uid %v", err) - } -} - -func TestRunAsAnyValidate(t *testing.T) { - s, err := NewRunAsAny(&policy.SELinuxStrategyOptions{ - SELinuxOptions: &corev1.SELinuxOptions{ - Level: "foo", - }, - }, - ) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - errs := s.Validate(nil, nil, nil, nil) - if len(errs) != 0 { - t.Errorf("unexpected errors validating with ") - } - s, err = NewRunAsAny(&policy.SELinuxStrategyOptions{}) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - errs = s.Validate(nil, nil, nil, nil) - if len(errs) != 0 { - t.Errorf("unexpected errors validating %v", errs) - } -} diff --git a/pkg/security/podsecuritypolicy/selinux/types.go b/pkg/security/podsecuritypolicy/selinux/types.go deleted file mode 100644 index cdaae809311..00000000000 --- a/pkg/security/podsecuritypolicy/selinux/types.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -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 selinux - -import ( - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// SELinuxStrategy defines the interface for all SELinux constraint strategies. -type SELinuxStrategy interface { - // Generate creates the SELinuxOptions based on constraint rules. - Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) - // Validate ensures that the specified values fall within the range of the strategy. - Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, options *api.SELinuxOptions) field.ErrorList -} diff --git a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go b/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go deleted file mode 100644 index 2c9b9e5b0f4..00000000000 --- a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -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 sysctl - -import ( - "fmt" - "strings" - - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// SafeSysctlAllowlist returns the allowlist of safe sysctls and safe sysctl patterns (ending in *). -// -// A sysctl is called safe iff -// - it is namespaced in the container or the pod -// - it is isolated, i.e. has no influence on any other pod on the same node. -func SafeSysctlAllowlist() []string { - return []string{ - "kernel.shm_rmid_forced", - "net.ipv4.ip_local_port_range", - "net.ipv4.tcp_syncookies", - "net.ipv4.ping_group_range", - "net.ipv4.ip_unprivileged_port_start", - } -} - -// mustMatchPatterns implements the SysctlsStrategy interface -type mustMatchPatterns struct { - safeAllowlist []string - allowedUnsafeSysctls []string - forbiddenSysctls []string -} - -var ( - _ SysctlsStrategy = &mustMatchPatterns{} -) - -// NewMustMatchPatterns creates a new mustMatchPatterns strategy that will provide validation. -// Passing nil means the default pattern, passing an empty list means to disallow all sysctls. -func NewMustMatchPatterns(safeAllowlist, allowedUnsafeSysctls, forbiddenSysctls []string) SysctlsStrategy { - return &mustMatchPatterns{ - safeAllowlist: safeAllowlist, - allowedUnsafeSysctls: allowedUnsafeSysctls, - forbiddenSysctls: forbiddenSysctls, - } -} - -func (s *mustMatchPatterns) isForbidden(sysctlName string) bool { - // Is the sysctl forbidden? - for _, s := range s.forbiddenSysctls { - if strings.HasSuffix(s, "*") { - prefix := strings.TrimSuffix(s, "*") - if strings.HasPrefix(sysctlName, prefix) { - return true - } - } else if sysctlName == s { - return true - } - } - return false -} - -func (s *mustMatchPatterns) isSafe(sysctlName string) bool { - for _, ws := range s.safeAllowlist { - if sysctlName == ws { - return true - } - } - return false -} - -func (s *mustMatchPatterns) isAllowedUnsafe(sysctlName string) bool { - for _, s := range s.allowedUnsafeSysctls { - if strings.HasSuffix(s, "*") { - prefix := strings.TrimSuffix(s, "*") - if strings.HasPrefix(sysctlName, prefix) { - return true - } - } else if sysctlName == s { - return true - } - } - return false -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList { - allErrs := field.ErrorList{} - - var sysctls []api.Sysctl - if pod.Spec.SecurityContext != nil { - sysctls = pod.Spec.SecurityContext.Sysctls - } - - fieldPath := field.NewPath("pod", "spec", "securityContext").Child("sysctls") - - for i, sysctl := range sysctls { - switch { - case s.isForbidden(sysctl.Name): - allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("sysctl %q is not allowed", sysctl.Name))}...) - case s.isSafe(sysctl.Name): - continue - case s.isAllowedUnsafe(sysctl.Name): - continue - default: - allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("unsafe sysctl %q is not allowed", sysctl.Name))}...) - } - } - - return allErrs -} diff --git a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go b/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go deleted file mode 100644 index a1073d7d6de..00000000000 --- a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -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 sysctl - -import ( - "testing" - - api "k8s.io/kubernetes/pkg/apis/core" -) - -func TestValidate(t *testing.T) { - tests := map[string]struct { - allowlist []string - forbiddenSafe []string - allowedUnsafe []string - allowed []string - disallowed []string - }{ - // no container requests - "with allow all": { - allowlist: []string{"foo"}, - allowed: []string{"foo"}, - }, - "empty": { - allowlist: []string{"foo"}, - forbiddenSafe: []string{"*"}, - disallowed: []string{"foo"}, - }, - "without wildcard": { - allowlist: []string{"a", "a.b"}, - allowed: []string{"a", "a.b"}, - disallowed: []string{"b"}, - }, - "with catch-all wildcard and non-wildcard": { - allowedUnsafe: []string{"a.b.c", "*"}, - allowed: []string{"a", "a.b", "a.b.c", "b"}, - }, - "without catch-all wildcard": { - allowedUnsafe: []string{"a.*", "b.*", "c.d.e", "d.e.f.*"}, - allowed: []string{"a.b", "b.c", "c.d.e", "d.e.f.g.h"}, - disallowed: []string{"a", "b", "c", "c.d", "d.e", "d.e.f"}, - }, - } - - for k, v := range tests { - strategy := NewMustMatchPatterns(v.allowlist, v.allowedUnsafe, v.forbiddenSafe) - - pod := &api.Pod{} - errs := strategy.Validate(pod) - if len(errs) != 0 { - t.Errorf("%s: unexpected validaton errors for empty sysctls: %v", k, errs) - } - - testAllowed := func() { - sysctls := []api.Sysctl{} - for _, s := range v.allowed { - sysctls = append(sysctls, api.Sysctl{ - Name: s, - Value: "dummy", - }) - } - pod.Spec.SecurityContext = &api.PodSecurityContext{ - Sysctls: sysctls, - } - errs = strategy.Validate(pod) - if len(errs) != 0 { - t.Errorf("%s: unexpected validaton errors for sysctls: %v", k, errs) - } - } - testDisallowed := func() { - for _, s := range v.disallowed { - pod.Spec.SecurityContext = &api.PodSecurityContext{ - Sysctls: []api.Sysctl{ - { - Name: s, - Value: "dummy", - }, - }, - } - errs = strategy.Validate(pod) - if len(errs) == 0 { - t.Errorf("%s: expected error for sysctl %q", k, s) - } - } - } - - testAllowed() - testDisallowed() - } -} diff --git a/pkg/security/podsecuritypolicy/types.go b/pkg/security/podsecuritypolicy/types.go deleted file mode 100644 index ea4694ae143..00000000000 --- a/pkg/security/podsecuritypolicy/types.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -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 podsecuritypolicy - -import ( - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" - "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" -) - -// Provider provides the implementation to generate a new security -// context based on constraints or validate an existing security context against constraints. -type Provider interface { - // MutatePod sets the default values of the required but not filled fields of the pod and all - // containers in the pod. - MutatePod(pod *api.Pod) error - // ValidatePod ensures a pod and all its containers are in compliance with the given constraints. - // ValidatePod MUST NOT mutate the pod. - ValidatePod(pod *api.Pod) field.ErrorList - // Get the name of the PSP that this provider was initialized with. - GetPSPName() string -} - -// StrategyFactory abstracts how the strategies are created from the provider so that you may -// implement your own custom strategies that may pull information from other resources as necessary. -// For example, if you would like to populate the strategies with values from namespace annotations -// you may create a factory with a client that can pull the namespace and populate the appropriate -// values. -type StrategyFactory interface { - // CreateStrategies creates the strategies that a provider will use. The namespace argument - // should be the namespace of the object being checked (the pod's namespace). - CreateStrategies(psp *policy.PodSecurityPolicy, namespace string) (*ProviderStrategies, error) -} - -// ProviderStrategies is a holder for all strategies that the provider requires to be populated. -type ProviderStrategies struct { - RunAsUserStrategy user.RunAsUserStrategy - RunAsGroupStrategy group.GroupStrategy - SELinuxStrategy selinux.SELinuxStrategy - AppArmorStrategy apparmor.Strategy - FSGroupStrategy group.GroupStrategy - SupplementalGroupStrategy group.GroupStrategy - CapabilitiesStrategy capabilities.Strategy - SysctlsStrategy sysctl.SysctlsStrategy - SeccompStrategy seccomp.Strategy -} diff --git a/pkg/security/podsecuritypolicy/user/doc.go b/pkg/security/podsecuritypolicy/user/doc.go deleted file mode 100644 index 1ddc08aee6d..00000000000 --- a/pkg/security/podsecuritypolicy/user/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -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 user contains code for validating and defaulting the UID of a pod -// or container according to a security policy. -package user diff --git a/pkg/security/podsecuritypolicy/user/mustrunas.go b/pkg/security/podsecuritypolicy/user/mustrunas.go deleted file mode 100644 index 93611b9afb2..00000000000 --- a/pkg/security/podsecuritypolicy/user/mustrunas.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -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 user - -import ( - "fmt" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" -) - -// mustRunAs implements the RunAsUserStrategy interface -type mustRunAs struct { - opts *policy.RunAsUserStrategyOptions -} - -// NewMustRunAs provides a strategy that requires the container to run as a specific UID in a range. -func NewMustRunAs(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { - if options == nil { - return nil, fmt.Errorf("MustRunAs requires run as user options") - } - if len(options.Ranges) == 0 { - return nil, fmt.Errorf("MustRunAs requires at least one range") - } - return &mustRunAs{ - opts: options, - }, nil -} - -// Generate creates the uid based on policy rules. MustRunAs returns the first range's Min. -func (s *mustRunAs) Generate(pod *api.Pod, container *api.Container) (*int64, error) { - return &s.opts.Ranges[0].Min, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *mustRunAs) Validate(scPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList { - allErrs := field.ErrorList{} - - if runAsUser == nil { - allErrs = append(allErrs, field.Required(scPath.Child("runAsUser"), "")) - return allErrs - } - - if !s.isValidUID(*runAsUser) { - detail := fmt.Sprintf("must be in the ranges: %v", s.opts.Ranges) - allErrs = append(allErrs, field.Invalid(scPath.Child("runAsUser"), *runAsUser, detail)) - } - return allErrs -} - -func (s *mustRunAs) isValidUID(id int64) bool { - for _, rng := range s.opts.Ranges { - if psputil.UserFallsInRange(id, rng) { - return true - } - } - return false -} diff --git a/pkg/security/podsecuritypolicy/user/mustrunas_test.go b/pkg/security/podsecuritypolicy/user/mustrunas_test.go deleted file mode 100644 index 8e05cefb2ab..00000000000 --- a/pkg/security/podsecuritypolicy/user/mustrunas_test.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -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 user - -import ( - policy "k8s.io/api/policy/v1beta1" - api "k8s.io/kubernetes/pkg/apis/core" - "strings" - "testing" -) - -func TestNewMustRunAs(t *testing.T) { - tests := map[string]struct { - opts *policy.RunAsUserStrategyOptions - pass bool - }{ - "nil opts": { - opts: nil, - pass: false, - }, - "invalid opts": { - opts: &policy.RunAsUserStrategyOptions{}, - pass: false, - }, - "valid opts": { - opts: &policy.RunAsUserStrategyOptions{ - Ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - }, - }, - pass: true, - }, - } - for name, tc := range tests { - _, err := NewMustRunAs(tc.opts) - if err != nil && tc.pass { - t.Errorf("%s expected to pass but received error %#v", name, err) - } - if err == nil && !tc.pass { - t.Errorf("%s expected to fail but did not receive an error", name) - } - } -} - -func TestGenerate(t *testing.T) { - opts := &policy.RunAsUserStrategyOptions{ - Ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - }, - } - mustRunAs, err := NewMustRunAs(opts) - if err != nil { - t.Fatalf("unexpected error initializing NewMustRunAs %v", err) - } - generated, err := mustRunAs.Generate(nil, nil) - if err != nil { - t.Fatalf("unexpected error generating runAsUser %v", err) - } - if *generated != opts.Ranges[0].Min { - t.Errorf("generated runAsUser does not equal configured runAsUser") - } -} - -func TestValidate(t *testing.T) { - opts := &policy.RunAsUserStrategyOptions{ - Ranges: []policy.IDRange{ - {Min: 1, Max: 1}, - {Min: 10, Max: 20}, - }, - } - - validID := int64(15) - invalidID := int64(21) - - tests := map[string]struct { - container *api.Container - expectedMsg string - }{ - "good container": { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsUser: &validID, - }, - }, - }, - "nil run as user": { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsUser: nil, - }, - }, - expectedMsg: "runAsUser: Required", - }, - "invalid id": { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsUser: &invalidID, - }, - }, - expectedMsg: "runAsUser: Invalid", - }, - } - - for name, tc := range tests { - mustRunAs, err := NewMustRunAs(opts) - if err != nil { - t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err) - continue - } - errs := mustRunAs.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser) - //should've passed but didn't - if len(tc.expectedMsg) == 0 && len(errs) > 0 { - t.Errorf("%s expected no errors but received %v", name, errs) - } - //should've failed but didn't - if len(tc.expectedMsg) != 0 && len(errs) == 0 { - t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg) - } - //failed with additional messages - if len(tc.expectedMsg) != 0 && len(errs) > 1 { - t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs) - } - //check that we got the right message - if len(tc.expectedMsg) != 0 && len(errs) == 1 { - if !strings.Contains(errs[0].Error(), tc.expectedMsg) { - t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs) - } - } - } -} diff --git a/pkg/security/podsecuritypolicy/user/nonroot.go b/pkg/security/podsecuritypolicy/user/nonroot.go deleted file mode 100644 index bde5b334a60..00000000000 --- a/pkg/security/podsecuritypolicy/user/nonroot.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -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 user - -import ( - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -type nonRoot struct{} - -var _ RunAsUserStrategy = &nonRoot{} - -func NewRunAsNonRoot(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { - return &nonRoot{}, nil -} - -// Generate creates the uid based on policy rules. This strategy does return a UID. It assumes -// that the user will specify a UID or the container image specifies a UID. -func (s *nonRoot) Generate(pod *api.Pod, container *api.Container) (*int64, error) { - return nil, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. Validation -// of this will pass if either the UID is not set, assuming that the image will provided the UID -// or if the UID is set it is not root. Validation will fail if RunAsNonRoot is set to false. -// In order to work properly this assumes that the kubelet performs a final check on runAsUser -// or the image UID when runAsUser is nil. -func (s *nonRoot) Validate(scPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList { - allErrs := field.ErrorList{} - if runAsNonRoot == nil && runAsUser == nil { - allErrs = append(allErrs, field.Required(scPath.Child("runAsNonRoot"), "must be true")) - return allErrs - } - if runAsNonRoot != nil && *runAsNonRoot == false { - allErrs = append(allErrs, field.Invalid(scPath.Child("runAsNonRoot"), *runAsNonRoot, "must be true")) - return allErrs - } - if runAsUser != nil && *runAsUser == 0 { - allErrs = append(allErrs, field.Invalid(scPath.Child("runAsUser"), *runAsUser, "running with the root UID is forbidden")) - return allErrs - } - return allErrs -} diff --git a/pkg/security/podsecuritypolicy/user/nonroot_test.go b/pkg/security/podsecuritypolicy/user/nonroot_test.go deleted file mode 100644 index cd1c0b861e3..00000000000 --- a/pkg/security/podsecuritypolicy/user/nonroot_test.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -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 user - -import ( - api "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - "testing" -) - -func TestNonRootOptions(t *testing.T) { - _, err := NewRunAsNonRoot(nil) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err) - } - _, err = NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{}) - if err != nil { - t.Errorf("unexpected error initializing NewRunAsNonRoot %v", err) - } -} - -func TestNonRootGenerate(t *testing.T) { - s, err := NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{}) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err) - } - uid, err := s.Generate(nil, nil) - if uid != nil { - t.Errorf("expected nil uid but got %d", *uid) - } - if err != nil { - t.Errorf("unexpected error generating uid %v", err) - } -} - -func TestNonRootValidate(t *testing.T) { - goodUID := int64(1) - badUID := int64(0) - untrue := false - unfalse := true - s, err := NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{}) - if err != nil { - t.Fatalf("unexpected error initializing NewMustRunAs %v", err) - } - tests := []struct { - container *api.Container - expectedErr bool - msg string - }{ - { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsUser: &badUID, - }, - }, - expectedErr: true, - msg: "in test case %d, expected errors from root uid but got none: %v", - }, - { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsUser: &goodUID, - }, - }, - expectedErr: false, - msg: "in test case %d, expected no errors from non-root uid but got %v", - }, - { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsNonRoot: &untrue, - }, - }, - expectedErr: true, - msg: "in test case %d, expected errors from RunAsNonRoot but got none: %v", - }, - { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsNonRoot: &unfalse, - RunAsUser: &goodUID, - }, - }, - expectedErr: false, - msg: "in test case %d, expected no errors from non-root uid but got %v", - }, - { - container: &api.Container{ - SecurityContext: &api.SecurityContext{ - RunAsNonRoot: nil, - RunAsUser: nil, - }, - }, - expectedErr: true, - msg: "in test case %d, expected errors from nil runAsNonRoot and nil runAsUser but got %v", - }, - } - - for i, tc := range tests { - errs := s.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser) - if (len(errs) == 0) == tc.expectedErr { - t.Errorf(tc.msg, i, errs) - } - } -} diff --git a/pkg/security/podsecuritypolicy/user/runasany.go b/pkg/security/podsecuritypolicy/user/runasany.go deleted file mode 100644 index 33cffe463b9..00000000000 --- a/pkg/security/podsecuritypolicy/user/runasany.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -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 user - -import ( - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// runAsAny implements the interface RunAsUserStrategy. -type runAsAny struct{} - -var _ RunAsUserStrategy = &runAsAny{} - -// NewRunAsAny provides a strategy that will return nil. -func NewRunAsAny(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { - return &runAsAny{}, nil -} - -// Generate creates the uid based on policy rules. -func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*int64, error) { - return nil, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *runAsAny) Validate(_ *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList { - return field.ErrorList{} -} diff --git a/pkg/security/podsecuritypolicy/user/runasany_test.go b/pkg/security/podsecuritypolicy/user/runasany_test.go deleted file mode 100644 index 1fe9926f9b7..00000000000 --- a/pkg/security/podsecuritypolicy/user/runasany_test.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -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 user - -import ( - "testing" - - policy "k8s.io/api/policy/v1beta1" -) - -func TestRunAsAnyOptions(t *testing.T) { - _, err := NewRunAsAny(nil) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - _, err = NewRunAsAny(&policy.RunAsUserStrategyOptions{}) - if err != nil { - t.Errorf("unexpected error initializing NewRunAsAny %v", err) - } -} - -func TestRunAsAnyGenerate(t *testing.T) { - s, err := NewRunAsAny(&policy.RunAsUserStrategyOptions{}) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - uid, err := s.Generate(nil, nil) - if uid != nil { - t.Errorf("expected nil uid but got %d", *uid) - } - if err != nil { - t.Errorf("unexpected error generating uid %v", err) - } -} - -func TestRunAsAnyValidate(t *testing.T) { - s, err := NewRunAsAny(&policy.RunAsUserStrategyOptions{}) - if err != nil { - t.Fatalf("unexpected error initializing NewRunAsAny %v", err) - } - errs := s.Validate(nil, nil, nil, nil, nil) - if len(errs) != 0 { - t.Errorf("unexpected errors validating with ") - } -} diff --git a/pkg/security/podsecuritypolicy/user/types.go b/pkg/security/podsecuritypolicy/user/types.go deleted file mode 100644 index 035d0607b44..00000000000 --- a/pkg/security/podsecuritypolicy/user/types.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -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 user - -import ( - "k8s.io/apimachinery/pkg/util/validation/field" - api "k8s.io/kubernetes/pkg/apis/core" -) - -// RunAsUserStrategy defines the interface for all uid constraint strategies. -type RunAsUserStrategy interface { - // Generate creates the uid based on policy rules. - Generate(pod *api.Pod, container *api.Container) (*int64, error) - // Validate ensures that the specified values fall within the range of the strategy. - // scPath is the field path to the container's security context - Validate(scPath *field.Path, pod *api.Pod, container *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList -} diff --git a/pkg/security/podsecuritypolicy/util/doc.go b/pkg/security/podsecuritypolicy/util/doc.go deleted file mode 100644 index af407b58c66..00000000000 --- a/pkg/security/podsecuritypolicy/util/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -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 util contains utility code shared amongst different parts of the -// pod security policy apparatus. -package util diff --git a/pkg/security/podsecuritypolicy/util/util.go b/pkg/security/podsecuritypolicy/util/util.go deleted file mode 100644 index 1405714f8bb..00000000000 --- a/pkg/security/podsecuritypolicy/util/util.go +++ /dev/null @@ -1,276 +0,0 @@ -/* -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 util - -import ( - "fmt" - "strings" - - policy "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/util/sets" - api "k8s.io/kubernetes/pkg/apis/core" -) - -const ( - ValidatedPSPAnnotation = "kubernetes.io/psp" -) - -// GetAllFSTypesExcept returns the result of GetAllFSTypesAsSet minus -// the given exceptions. -func GetAllFSTypesExcept(exceptions ...string) sets.String { - fstypes := GetAllFSTypesAsSet() - for _, e := range exceptions { - fstypes.Delete(e) - } - return fstypes -} - -// GetAllFSTypesAsSet returns all actual volume types, regardless -// of feature gates. The special policy.All pseudo type is not included. -func GetAllFSTypesAsSet() sets.String { - fstypes := sets.NewString() - fstypes.Insert( - string(policy.HostPath), - string(policy.AzureFile), - string(policy.Flocker), - string(policy.FlexVolume), - string(policy.EmptyDir), - string(policy.GCEPersistentDisk), - string(policy.AWSElasticBlockStore), - string(policy.GitRepo), - string(policy.Secret), - string(policy.NFS), - string(policy.ISCSI), - string(policy.Glusterfs), - string(policy.PersistentVolumeClaim), - string(policy.RBD), - string(policy.Cinder), - string(policy.CephFS), - string(policy.DownwardAPI), - string(policy.FC), - string(policy.ConfigMap), - string(policy.VsphereVolume), - string(policy.Quobyte), - string(policy.AzureDisk), - string(policy.PhotonPersistentDisk), - string(policy.StorageOS), - string(policy.Projected), - string(policy.PortworxVolume), - string(policy.ScaleIO), - string(policy.CSI), - string(policy.Ephemeral), - ) - return fstypes -} - -// getVolumeFSType gets the FSType for a volume. -func GetVolumeFSType(v api.Volume) (policy.FSType, error) { - switch { - case v.HostPath != nil: - return policy.HostPath, nil - case v.EmptyDir != nil: - return policy.EmptyDir, nil - case v.GCEPersistentDisk != nil: - return policy.GCEPersistentDisk, nil - case v.AWSElasticBlockStore != nil: - return policy.AWSElasticBlockStore, nil - case v.GitRepo != nil: - return policy.GitRepo, nil - case v.Secret != nil: - return policy.Secret, nil - case v.NFS != nil: - return policy.NFS, nil - case v.ISCSI != nil: - return policy.ISCSI, nil - case v.Glusterfs != nil: - return policy.Glusterfs, nil - case v.PersistentVolumeClaim != nil: - return policy.PersistentVolumeClaim, nil - case v.RBD != nil: - return policy.RBD, nil - case v.FlexVolume != nil: - return policy.FlexVolume, nil - case v.Cinder != nil: - return policy.Cinder, nil - case v.CephFS != nil: - return policy.CephFS, nil - case v.Flocker != nil: - return policy.Flocker, nil - case v.DownwardAPI != nil: - return policy.DownwardAPI, nil - case v.FC != nil: - return policy.FC, nil - case v.AzureFile != nil: - return policy.AzureFile, nil - case v.ConfigMap != nil: - return policy.ConfigMap, nil - case v.VsphereVolume != nil: - return policy.VsphereVolume, nil - case v.Quobyte != nil: - return policy.Quobyte, nil - case v.AzureDisk != nil: - return policy.AzureDisk, nil - case v.PhotonPersistentDisk != nil: - return policy.PhotonPersistentDisk, nil - case v.StorageOS != nil: - return policy.StorageOS, nil - case v.Projected != nil: - return policy.Projected, nil - case v.PortworxVolume != nil: - return policy.PortworxVolume, nil - case v.ScaleIO != nil: - return policy.ScaleIO, nil - case v.CSI != nil: - return policy.CSI, nil - case v.Ephemeral != nil: - return policy.Ephemeral, nil - } - - return "", fmt.Errorf("unknown volume type for volume: %#v", v) -} - -// FSTypeToStringSet converts an FSType slice to a string set. -func FSTypeToStringSet(fsTypes []policy.FSType) sets.String { - set := sets.NewString() - for _, v := range fsTypes { - set.Insert(string(v)) - } - return set -} - -// PSPAllowsAllVolumes checks for FSTypeAll in the psp's allowed volumes. -func PSPAllowsAllVolumes(psp *policy.PodSecurityPolicy) bool { - return PSPAllowsFSType(psp, policy.All) -} - -// PSPAllowsFSType is a utility for checking if a PSP allows a particular FSType. -// If all volumes are allowed then this will return true for any FSType passed. -func PSPAllowsFSType(psp *policy.PodSecurityPolicy, fsType policy.FSType) bool { - if psp == nil { - return false - } - - for _, v := range psp.Spec.Volumes { - if v == fsType || v == policy.All { - return true - } - } - return false -} - -// UserFallsInRange is a utility to determine it the id falls in the valid range. -func UserFallsInRange(id int64, rng policy.IDRange) bool { - return id >= rng.Min && id <= rng.Max -} - -// GroupFallsInRange is a utility to determine it the id falls in the valid range. -func GroupFallsInRange(id int64, rng policy.IDRange) bool { - return id >= rng.Min && id <= rng.Max -} - -// AllowsHostVolumePath is a utility for checking if a PSP allows the host volume path. -// This only checks the path. You should still check to make sure the host volume fs type is allowed. -func AllowsHostVolumePath(psp *policy.PodSecurityPolicy, hostPath string) (pathIsAllowed, mustBeReadOnly bool) { - if psp == nil { - return false, false - } - - // If no allowed paths are specified then allow any path - if len(psp.Spec.AllowedHostPaths) == 0 { - return true, false - } - - for _, allowedPath := range psp.Spec.AllowedHostPaths { - if hasPathPrefix(hostPath, allowedPath.PathPrefix) { - if !allowedPath.ReadOnly { - return true, allowedPath.ReadOnly - } - pathIsAllowed = true - mustBeReadOnly = true - } - } - - return pathIsAllowed, mustBeReadOnly -} - -// hasPathPrefix returns true if the string matches pathPrefix exactly, or if is prefixed with pathPrefix at a path segment boundary -// the string and pathPrefix are both normalized to remove trailing slashes prior to checking. -func hasPathPrefix(s, pathPrefix string) bool { - - s = strings.TrimSuffix(s, "/") - pathPrefix = strings.TrimSuffix(pathPrefix, "/") - - // Short circuit if s doesn't contain the prefix at all - if !strings.HasPrefix(s, pathPrefix) { - return false - } - - pathPrefixLength := len(pathPrefix) - - if len(s) == pathPrefixLength { - // Exact match - return true - } - - if s[pathPrefixLength:pathPrefixLength+1] == "/" { - // The next character in s is a path segment boundary - // Check this instead of normalizing pathPrefix to avoid allocating on every call - // Example where this check applies: s=/foo/bar and pathPrefix=/foo - return true - } - - return false -} - -// EqualStringSlices compares string slices for equality. Slices are equal when -// their sizes and elements on similar positions are equal. -func EqualStringSlices(a, b []string) bool { - if len(a) != len(b) { - return false - } - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - return true -} - -func IsOnlyServiceAccountTokenSources(v *api.ProjectedVolumeSource) bool { - for _, s := range v.Sources { - // reject any projected source that does not match any of our expected source types - if s.ServiceAccountToken == nil && s.ConfigMap == nil && s.DownwardAPI == nil { - return false - } - if t := s.ServiceAccountToken; t != nil && (t.Path != "token" || t.Audience != "") { - return false - } - - if s.ConfigMap != nil && s.ConfigMap.LocalObjectReference.Name != "kube-root-ca.crt" { - return false - } - - if s.DownwardAPI != nil { - for _, d := range s.DownwardAPI.Items { - if d.Path != "namespace" || d.FieldRef == nil || d.FieldRef.APIVersion != "v1" || d.FieldRef.FieldPath != "metadata.namespace" { - return false - } - } - } - } - return true -} diff --git a/pkg/security/podsecuritypolicy/util/util_test.go b/pkg/security/podsecuritypolicy/util/util_test.go deleted file mode 100644 index ceef7dc0dc9..00000000000 --- a/pkg/security/podsecuritypolicy/util/util_test.go +++ /dev/null @@ -1,471 +0,0 @@ -/* -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 util - -import ( - "reflect" - "testing" - - policy "k8s.io/api/policy/v1beta1" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/serviceaccount" -) - -// TestVolumeSourceFSTypeDrift ensures that for every known type of volume source (by the fields on -// a VolumeSource object that GetVolumeFSType is returning a good value. This ensures both that we're -// returning an FSType for the VolumeSource field (protect the GetVolumeFSType method) and that we -// haven't drifted (ensure new fields in VolumeSource are covered). -func TestVolumeSourceFSTypeDrift(t *testing.T) { - allFSTypes := GetAllFSTypesAsSet() - val := reflect.ValueOf(api.VolumeSource{}) - - for i := 0; i < val.NumField(); i++ { - fieldVal := val.Type().Field(i) - - volumeSource := api.VolumeSource{} - volumeSourceVolume := reflect.New(fieldVal.Type.Elem()) - - reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume) - - fsType, err := GetVolumeFSType(api.Volume{VolumeSource: volumeSource}) - if err != nil { - t.Errorf("error getting fstype for field %s. This likely means that drift has occurred between FSType and VolumeSource. Please update the api and getVolumeFSType", fieldVal.Name) - } - - if !allFSTypes.Has(string(fsType)) { - t.Errorf("%s was missing from GetFSTypesAsSet", fsType) - } - } -} - -func TestPSPAllowsFSType(t *testing.T) { - tests := map[string]struct { - psp *policy.PodSecurityPolicy - fsType policy.FSType - allows bool - }{ - "nil psp": { - psp: nil, - fsType: policy.HostPath, - allows: false, - }, - "empty volumes": { - psp: &policy.PodSecurityPolicy{}, - fsType: policy.HostPath, - allows: false, - }, - "non-matching": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - Volumes: []policy.FSType{policy.AWSElasticBlockStore}, - }, - }, - fsType: policy.HostPath, - allows: false, - }, - "match on FSTypeAll": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - Volumes: []policy.FSType{policy.All}, - }, - }, - fsType: policy.HostPath, - allows: true, - }, - "match on direct match": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - Volumes: []policy.FSType{policy.HostPath}, - }, - }, - fsType: policy.HostPath, - allows: true, - }, - } - - for k, v := range tests { - allows := PSPAllowsFSType(v.psp, v.fsType) - if v.allows != allows { - t.Errorf("%s expected PSPAllowsFSType to return %t but got %t", k, v.allows, allows) - } - } -} - -func TestAllowsHostVolumePath(t *testing.T) { - tests := map[string]struct { - psp *policy.PodSecurityPolicy - path string - allows bool - mustBeReadOnly bool - }{ - "nil psp": { - psp: nil, - path: "/test", - allows: false, - mustBeReadOnly: false, - }, - "empty allowed paths": { - psp: &policy.PodSecurityPolicy{}, - path: "/test", - allows: true, - mustBeReadOnly: false, - }, - "non-matching": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - AllowedHostPaths: []policy.AllowedHostPath{ - { - PathPrefix: "/foo", - ReadOnly: true, - }, - }, - }, - }, - path: "/foobar", - allows: false, - mustBeReadOnly: false, - }, - "match on direct match": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - AllowedHostPaths: []policy.AllowedHostPath{ - { - PathPrefix: "/foo", - ReadOnly: true, - }, - }, - }, - }, - path: "/foo", - allows: true, - mustBeReadOnly: true, - }, - "match with trailing slash on host path": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - AllowedHostPaths: []policy.AllowedHostPath{ - {PathPrefix: "/foo"}, - }, - }, - }, - path: "/foo/", - allows: true, - mustBeReadOnly: false, - }, - "match with trailing slash on allowed path": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - AllowedHostPaths: []policy.AllowedHostPath{ - {PathPrefix: "/foo/"}, - }, - }, - }, - path: "/foo", - allows: true, - mustBeReadOnly: false, - }, - "match child directory": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - AllowedHostPaths: []policy.AllowedHostPath{ - { - PathPrefix: "/foo/", - ReadOnly: true, - }, - }, - }, - }, - path: "/foo/bar", - allows: true, - mustBeReadOnly: true, - }, - "non-matching parent directory": { - psp: &policy.PodSecurityPolicy{ - Spec: policy.PodSecurityPolicySpec{ - AllowedHostPaths: []policy.AllowedHostPath{ - {PathPrefix: "/foo/bar"}, - }, - }, - }, - path: "/foo", - allows: false, - mustBeReadOnly: false, - }, - } - - for k, v := range tests { - allows, mustBeReadOnly := AllowsHostVolumePath(v.psp, v.path) - if v.allows != allows { - t.Errorf("allows: %s expected %t but got %t", k, v.allows, allows) - } - if v.mustBeReadOnly != mustBeReadOnly { - t.Errorf("mustBeReadOnly: %s expected %t but got %t", k, v.mustBeReadOnly, mustBeReadOnly) - } - } -} - -func TestEqualStringSlices(t *testing.T) { - tests := map[string]struct { - arg1 []string - arg2 []string - expectedResult bool - }{ - "nil equals to nil": { - arg1: nil, - arg2: nil, - expectedResult: true, - }, - "equal by size": { - arg1: []string{"1", "1"}, - arg2: []string{"1", "1"}, - expectedResult: true, - }, - "not equal by size": { - arg1: []string{"1"}, - arg2: []string{"1", "1"}, - expectedResult: false, - }, - "not equal by elements": { - arg1: []string{"1", "1"}, - arg2: []string{"1", "2"}, - expectedResult: false, - }, - } - - for k, v := range tests { - if result := EqualStringSlices(v.arg1, v.arg2); result != v.expectedResult { - t.Errorf("%s expected to return %t but got %t", k, v.expectedResult, result) - } - } -} - -func TestIsOnlyServiceAccountTokenSources(t *testing.T) { - serviceAccountToken := api.VolumeProjection{ - ServiceAccountToken: &api.ServiceAccountTokenProjection{ - Path: "token", - ExpirationSeconds: serviceaccount.WarnOnlyBoundTokenExpirationSeconds, - }} - configMap := api.VolumeProjection{ - ConfigMap: &api.ConfigMapProjection{ - LocalObjectReference: api.LocalObjectReference{ - Name: "kube-root-ca.crt", - }, - Items: []api.KeyToPath{ - { - Key: "ca.crt", - Path: "ca.crt", - }, - }, - }, - } - downwardAPI := api.VolumeProjection{ - DownwardAPI: &api.DownwardAPIProjection{ - Items: []api.DownwardAPIVolumeFile{ - { - Path: "namespace", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.namespace", - }, - }, - }, - }, - } - - tests := []struct { - desc string - volume *api.ProjectedVolumeSource - want bool - }{ - { - desc: "deny if ServiceAccountToken has wrong path", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - {ServiceAccountToken: &api.ServiceAccountTokenProjection{ - Path: "notatoken", - ExpirationSeconds: serviceaccount.WarnOnlyBoundTokenExpirationSeconds, - }}, - configMap, - downwardAPI, - }, - }, - }, - { - desc: "deny if ServiceAccountToken has wrong audience", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - {ServiceAccountToken: &api.ServiceAccountTokenProjection{ - Path: "token", - Audience: "not api server", - ExpirationSeconds: serviceaccount.WarnOnlyBoundTokenExpirationSeconds, - }}, - configMap, - downwardAPI, - }, - }, - }, - { - desc: "deny if CondigMap has wrong LocalObjectReference.Name", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - serviceAccountToken, - { - ConfigMap: &api.ConfigMapProjection{ - LocalObjectReference: api.LocalObjectReference{ - Name: "foo-ca.crt", - }, - Items: []api.KeyToPath{ - { - Key: "ca.crt", - Path: "ca.crt", - }, - }, - }, - }, - downwardAPI, - }, - }, - }, - { - desc: "deny if DownwardAPI has wrong path", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - serviceAccountToken, - configMap, - { - DownwardAPI: &api.DownwardAPIProjection{ - Items: []api.DownwardAPIVolumeFile{ - { - Path: "foo", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.namespace", - }, - }, - }, - }, - }, - }, - }, - }, - { - desc: "deny if DownwardAPI has nil field ref", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - serviceAccountToken, - configMap, - { - DownwardAPI: &api.DownwardAPIProjection{ - Items: []api.DownwardAPIVolumeFile{ - { - Path: "namespace", - }, - }, - }, - }, - }, - }, - }, - { - desc: "deny if DownwardAPI has wrong api version", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - serviceAccountToken, - configMap, - { - DownwardAPI: &api.DownwardAPIProjection{ - Items: []api.DownwardAPIVolumeFile{ - { - Path: "namespace", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1beta1", - FieldPath: "metadata.namespace", - }, - }, - }, - }, - }, - }, - }, - }, - { - desc: "deny if DownwardAPI has wrong field path", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - serviceAccountToken, - configMap, - { - DownwardAPI: &api.DownwardAPIProjection{ - Items: []api.DownwardAPIVolumeFile{ - { - Path: "namespace", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.foo", - }, - }, - }, - }, - }, - }, - }, - }, - { - desc: "deny if Secret exists", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - { - Secret: &api.SecretProjection{}, - }, - configMap, - downwardAPI, - serviceAccountToken, - }, - }, - }, - { - desc: "deny if none of ServiceAccountToken, ConfigMap and DownwardAPI exist", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - {}, - }, - }, - }, - { - desc: "allow if any of ServiceAccountToken, ConfigMap and DownwardAPI matches", - volume: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{ - configMap, - downwardAPI, - serviceAccountToken, - }, - }, - want: true, - }, - } - - for _, test := range tests { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - if got := IsOnlyServiceAccountTokenSources(test.volume); got != test.want { - t.Errorf("IsOnlyServiceAccountTokenSources(%+v) = %v, want %v", test.volume, got, test.want) - } - }) - } -} diff --git a/plugin/pkg/admission/security/podsecuritypolicy/OWNERS b/plugin/pkg/admission/security/podsecuritypolicy/OWNERS deleted file mode 100644 index 50dc329afb3..00000000000 --- a/plugin/pkg/admission/security/podsecuritypolicy/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -approvers: - - sig-auth-policy-approvers -reviewers: - - sig-auth-policy-reviewers -labels: - - sig/auth diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission.go b/plugin/pkg/admission/security/podsecuritypolicy/admission.go deleted file mode 100644 index ef4966a4a35..00000000000 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission.go +++ /dev/null @@ -1,380 +0,0 @@ -/* -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 podsecuritypolicy - -import ( - "context" - "fmt" - "io" - "sort" - "strings" - - "k8s.io/klog/v2" - - policyv1beta1 "k8s.io/api/policy/v1beta1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" - "k8s.io/apiserver/pkg/authentication/serviceaccount" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/client-go/informers" - policylisters "k8s.io/client-go/listers/policy/v1beta1" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/apis/policy" - rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" - psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" -) - -// PluginName is a string with the name of the plugin -const PluginName = "PodSecurityPolicy" - -// Register registers a plugin -func Register(plugins *admission.Plugins) { - plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { - plugin := newPlugin(psp.NewSimpleStrategyFactory(), true) - return plugin, nil - }) -} - -// Plugin holds state for and implements the admission plugin. -type Plugin struct { - *admission.Handler - strategyFactory psp.StrategyFactory - failOnNoPolicies bool - authz authorizer.Authorizer - lister policylisters.PodSecurityPolicyLister -} - -// SetAuthorizer sets the authorizer. -func (p *Plugin) SetAuthorizer(authz authorizer.Authorizer) { - p.authz = authz -} - -// ValidateInitialization ensures an authorizer is set. -func (p *Plugin) ValidateInitialization() error { - if p.authz == nil { - return fmt.Errorf("%s requires an authorizer", PluginName) - } - if p.lister == nil { - return fmt.Errorf("%s requires a lister", PluginName) - } - return nil -} - -var _ admission.MutationInterface = &Plugin{} -var _ admission.ValidationInterface = &Plugin{} -var _ genericadmissioninit.WantsAuthorizer = &Plugin{} -var _ genericadmissioninit.WantsExternalKubeInformerFactory = &Plugin{} -var auditKeyPrefix = strings.ToLower(PluginName) + "." + policy.GroupName + ".k8s.io" - -// newPlugin creates a new PSP admission plugin. -func newPlugin(strategyFactory psp.StrategyFactory, failOnNoPolicies bool) *Plugin { - return &Plugin{ - Handler: admission.NewHandler(admission.Create, admission.Update), - strategyFactory: strategyFactory, - failOnNoPolicies: failOnNoPolicies, - } -} - -// SetExternalKubeInformerFactory registers an informer -func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { - podSecurityPolicyInformer := f.Policy().V1beta1().PodSecurityPolicies() - p.lister = podSecurityPolicyInformer.Lister() - p.SetReadyFunc(podSecurityPolicyInformer.Informer().HasSynced) -} - -// Admit determines if the pod should be admitted based on the requested security context -// and the available PSPs. -// -// 1. Find available PSPs. -// 2. Create the providers, includes setting pre-allocated values if necessary. -// 3. Try to generate and validate a PSP with providers. If we find one then admit the pod -// with the validated PSP. If we don't find any reject the pod and give all errors from the -// failed attempts. -func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { - if ignore, err := shouldIgnore(a); err != nil { - return err - } else if ignore { - return nil - } - - // only mutate if this is a CREATE request. On updates we only validate. - if a.GetOperation() != admission.Create { - return nil - } - - pod := a.GetObject().(*api.Pod) - - // compute the context. Mutation is allowed. ValidatedPSPAnnotation is not taken into account. - allowedPod, pspName, validationErrs, err := p.computeSecurityContext(ctx, a, pod, true, "") - if err != nil { - return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: %w", err)) - } - if allowedPod != nil { - *pod = *allowedPod - // annotate and accept the pod - klog.V(4).Infof("pod %s (generate: %s) in namespace %s validated against provider %s", pod.Name, pod.GenerateName, a.GetNamespace(), pspName) - if pod.ObjectMeta.Annotations == nil { - pod.ObjectMeta.Annotations = map[string]string{} - } - pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = pspName - key := auditKeyPrefix + "/" + "admit-policy" - if err := a.AddAnnotation(key, pspName); err != nil { - klog.Warningf("failed to set admission audit annotation %s to %s: %v", key, pspName, err) - } - return nil - } - - // we didn't validate against any provider, reject the pod and give the errors for each attempt - klog.V(4).Infof("unable to admit pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs) - return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: unable to admit pod: %v", validationErrs)) -} - -// Validate verifies attributes against the PodSecurityPolicy -func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { - if ignore, err := shouldIgnore(a); err != nil { - return err - } else if ignore { - return nil - } - - pod := a.GetObject().(*api.Pod) - - // compute the context. Mutation is not allowed. ValidatedPSPAnnotation is used as a hint to gain same speed-up. - allowedPod, pspName, validationErrs, err := p.computeSecurityContext(ctx, a, pod, false, pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation]) - if err != nil { - return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: %w", err)) - } - if apiequality.Semantic.DeepEqual(pod, allowedPod) { - key := auditKeyPrefix + "/" + "validate-policy" - if err := a.AddAnnotation(key, pspName); err != nil { - klog.Warningf("failed to set admission audit annotation %s to %s: %v", key, pspName, err) - } - return nil - } - - // we didn't validate against any provider, reject the pod and give the errors for each attempt - klog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs) - return admission.NewForbidden(a, fmt.Errorf("PodSecurityPolicy: unable to validate pod: %v", validationErrs)) -} - -func shouldIgnore(a admission.Attributes) (bool, error) { - if a.GetResource().GroupResource() != api.Resource("pods") { - return true, nil - } - if len(a.GetSubresource()) != 0 { - return true, nil - } - - // if we can't convert then fail closed since we've already checked that this is supposed to be a pod object. - // this shouldn't normally happen during admission but could happen if an integrator passes a versioned - // pod object rather than an internal object. - if _, ok := a.GetObject().(*api.Pod); !ok { - return false, admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) - } - - // if this is an update, see if we are only updating the ownerRef/finalizers. Garbage collection does this - // and we should allow it in general, since you had the power to update and the power to delete. - // The worst that happens is that you delete something, but you aren't controlling the privileged object itself - if a.GetOperation() == admission.Update && rbacregistry.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), apiequality.Semantic) { - return true, nil - } - - return false, nil -} - -// computeSecurityContext derives a valid security context while trying to avoid any changes to the given pod. I.e. -// if there is a matching policy with the same security context as given, it will be reused. If there is no -// matching policy the returned pod will be nil and the pspName empty. validatedPSPHint is the validated psp name -// saved in kubernetes.io/psp annotation. This psp is usually the one we are looking for. -func (p *Plugin) computeSecurityContext(ctx context.Context, a admission.Attributes, pod *api.Pod, specMutationAllowed bool, validatedPSPHint string) (*api.Pod, string, field.ErrorList, error) { - // get all constraints that are usable by the user - klog.V(4).Infof("getting pod security policies for pod %s (generate: %s)", pod.Name, pod.GenerateName) - var saInfo user.Info - if len(pod.Spec.ServiceAccountName) > 0 { - saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") - } - - policies, err := p.lister.List(labels.Everything()) - if err != nil { - return nil, "", nil, err - } - - // if we have no policies and want to succeed then return. Otherwise we'll end up with no - // providers and fail with "unable to validate against any pod security policy" below. - if len(policies) == 0 && !p.failOnNoPolicies { - return pod, "", nil, nil - } - - // sort policies by name to make order deterministic - // If mutation is not allowed and validatedPSPHint is provided, check the validated policy first. - sort.SliceStable(policies, func(i, j int) bool { - if !specMutationAllowed { - if policies[i].Name == validatedPSPHint { - return true - } - if policies[j].Name == validatedPSPHint { - return false - } - } - return strings.Compare(policies[i].Name, policies[j].Name) < 0 - }) - - providers, errs := p.createProvidersFromPolicies(policies, pod.Namespace) - for _, err := range errs { - klog.V(4).Infof("provider creation error: %v", err) - } - - if len(providers) == 0 { - return nil, "", nil, fmt.Errorf("no providers available to validate pod request") - } - - var ( - allowedMutatedPod *api.Pod - allowingMutatingPSP string - // Map of PSP name to associated validation errors. - validationErrs = map[string]field.ErrorList{} - ) - - for _, provider := range providers { - podCopy := pod.DeepCopy() - - if errs := assignSecurityContext(provider, podCopy); len(errs) > 0 { - validationErrs[provider.GetPSPName()] = errs - continue - } - - // the entire pod validated - mutated := !apiequality.Semantic.DeepEqual(pod, podCopy) - if mutated && !specMutationAllowed { - continue - } - - if !isAuthorizedForPolicy(ctx, a.GetUserInfo(), saInfo, a.GetNamespace(), provider.GetPSPName(), p.authz) { - continue - } - - switch { - case !mutated: - // if it validated without mutating anything, use this result - return podCopy, provider.GetPSPName(), nil, nil - - case specMutationAllowed && allowedMutatedPod == nil: - // if mutation is allowed and this is the first PSP to allow the pod, remember it, - // but continue to see if another PSP allows without mutating - allowedMutatedPod = podCopy - allowingMutatingPSP = provider.GetPSPName() - } - } - - if allowedMutatedPod != nil { - return allowedMutatedPod, allowingMutatingPSP, nil, nil - } - - // Pod is rejected. Filter the validation errors to only include errors from authorized PSPs. - aggregate := field.ErrorList{} - for psp, errs := range validationErrs { - if isAuthorizedForPolicy(ctx, a.GetUserInfo(), saInfo, a.GetNamespace(), psp, p.authz) { - aggregate = append(aggregate, errs...) - } - } - return nil, "", aggregate, nil -} - -// assignSecurityContext creates a security context for each container in the pod -// and validates that the sc falls within the psp constraints. All containers must validate against -// the same psp or is not considered valid. -func assignSecurityContext(provider psp.Provider, pod *api.Pod) field.ErrorList { - errs := field.ErrorList{} - - if err := provider.MutatePod(pod); err != nil { - // TODO(tallclair): MutatePod should return a field.ErrorList - errs = append(errs, field.Invalid(field.NewPath(""), pod, err.Error())) - } - - errs = append(errs, provider.ValidatePod(pod)...) - - return errs -} - -// createProvidersFromPolicies creates providers from the constraints supplied. -func (p *Plugin) createProvidersFromPolicies(psps []*policyv1beta1.PodSecurityPolicy, namespace string) ([]psp.Provider, []error) { - var ( - // collected providers - providers []psp.Provider - // collected errors to return - errs []error - ) - - for _, constraint := range psps { - provider, err := psp.NewSimpleProvider(constraint, namespace, p.strategyFactory) - if err != nil { - errs = append(errs, fmt.Errorf("error creating provider for PSP %s: %v", constraint.Name, err)) - continue - } - providers = append(providers, provider) - } - return providers, errs -} - -func isAuthorizedForPolicy(ctx context.Context, user, sa user.Info, namespace, policyName string, authz authorizer.Authorizer) bool { - // Check the service account first, as that is the more common use case. - return authorizedForPolicy(ctx, sa, namespace, policyName, authz) || - authorizedForPolicy(ctx, user, namespace, policyName, authz) -} - -// authorizedForPolicy returns true if info is authorized to perform the "use" verb on the policy resource. -// TODO: check against only the policy group when PSP will be completely moved out of the extensions -func authorizedForPolicy(ctx context.Context, info user.Info, namespace string, policyName string, authz authorizer.Authorizer) bool { - // Check against extensions API group for backward compatibility - return authorizedForPolicyInAPIGroup(ctx, info, namespace, policyName, policy.GroupName, authz) || - authorizedForPolicyInAPIGroup(ctx, info, namespace, policyName, extensions.GroupName, authz) -} - -// authorizedForPolicyInAPIGroup returns true if info is authorized to perform the "use" verb on the policy resource in the specified API group. -func authorizedForPolicyInAPIGroup(ctx context.Context, info user.Info, namespace, policyName, apiGroupName string, authz authorizer.Authorizer) bool { - if info == nil { - return false - } - attr := buildAttributes(info, namespace, policyName, apiGroupName) - decision, reason, err := authz.Authorize(ctx, attr) - if err != nil { - klog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err) - } - return (decision == authorizer.DecisionAllow) -} - -// buildAttributes builds an attributes record for a SAR based on the user info and policy. -func buildAttributes(info user.Info, namespace, policyName, apiGroupName string) authorizer.Attributes { - // check against the namespace that the pod is being created in to allow per-namespace PSP grants. - attr := authorizer.AttributesRecord{ - User: info, - Verb: "use", - Namespace: namespace, - Name: policyName, - APIGroup: apiGroupName, - APIVersion: "*", - Resource: "podsecuritypolicies", - ResourceRequest: true, - } - return attr -} diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go deleted file mode 100644 index 27993988d73..00000000000 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go +++ /dev/null @@ -1,2451 +0,0 @@ -/* -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 podsecuritypolicy - -import ( - "context" - "fmt" - "reflect" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - v1 "k8s.io/api/core/v1" - policy "k8s.io/api/policy/v1beta1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/diff" - kadmission "k8s.io/apiserver/pkg/admission" - admissiontesting "k8s.io/apiserver/pkg/admission/testing" - "k8s.io/apiserver/pkg/authentication/serviceaccount" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/apiserver/pkg/authorization/authorizerfactory" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/client-go/informers" - featuregatetesting "k8s.io/component-base/featuregate/testing" - "k8s.io/kubernetes/pkg/api/legacyscheme" - kapi "k8s.io/kubernetes/pkg/apis/core" - k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/features" - "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" - utilpointer "k8s.io/utils/pointer" -) - -const defaultContainerName = "test-c" - -// NewTestAdmission provides an admission plugin with test implementations of internal structs. -func NewTestAdmission(psps []*policy.PodSecurityPolicy, authz authorizer.Authorizer) *Plugin { - informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) - store := informerFactory.Policy().V1beta1().PodSecurityPolicies().Informer().GetStore() - for _, psp := range psps { - store.Add(psp) - } - lister := informerFactory.Policy().V1beta1().PodSecurityPolicies().Lister() - if authz == nil { - authz = &TestAuthorizer{} - } - return &Plugin{ - Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), - strategyFactory: kpsp.NewSimpleStrategyFactory(), - authz: authz, - lister: lister, - } -} - -// TestAuthorizer is a testing struct for testing that fulfills the authorizer interface. -type TestAuthorizer struct { - // usernameToNamespaceToAllowedPSPs contains the map of allowed PSPs. - // if nil, all PSPs are allowed. - usernameToNamespaceToAllowedPSPs map[string]map[string]map[string]bool - // allowedAPIGroupName specifies an API Group name that contains PSP resources. - // In order to be authorized, AttributesRecord must have this group name. - // When empty, API Group name isn't taken into account. - // TODO: remove this when PSP will be completely moved out of the extensions and we'll lookup only in "policy" group. - allowedAPIGroupName string -} - -func (t *TestAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - if t.usernameToNamespaceToAllowedPSPs == nil { - return authorizer.DecisionAllow, "", nil - } - allowedInNamespace := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][a.GetNamespace()][a.GetName()] - allowedClusterWide := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][""][a.GetName()] - allowedAPIGroup := len(t.allowedAPIGroupName) == 0 || a.GetAPIGroup() == t.allowedAPIGroupName - if allowedAPIGroup && (allowedInNamespace || allowedClusterWide) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionNoOpinion, "", nil -} - -var _ authorizer.Authorizer = &TestAuthorizer{} - -func useInitContainers(pod *kapi.Pod) *kapi.Pod { - pod.Spec.InitContainers = pod.Spec.Containers - pod.Spec.Containers = []kapi.Container{} - return pod -} - -func TestAdmitSeccomp(t *testing.T) { - containerName := "container" - tests := map[string]struct { - pspAnnotations map[string]string - podAnnotations map[string]string - shouldPassAdmit bool - shouldPassValidate bool - }{ - "no seccomp, no pod annotations": { - pspAnnotations: nil, - podAnnotations: nil, - shouldPassAdmit: true, - shouldPassValidate: true, - }, - "no seccomp, pod annotations": { - pspAnnotations: nil, - podAnnotations: map[string]string{ - kapi.SeccompPodAnnotationKey: "foo", - }, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "no seccomp, container annotations": { - pspAnnotations: nil, - podAnnotations: map[string]string{ - kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo", - }, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "seccomp, allow any no pod annotation": { - pspAnnotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny, - }, - podAnnotations: nil, - shouldPassAdmit: true, - shouldPassValidate: true, - }, - "seccomp, allow any pod annotation": { - pspAnnotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny, - }, - podAnnotations: map[string]string{ - kapi.SeccompPodAnnotationKey: "foo", - }, - shouldPassAdmit: true, - shouldPassValidate: true, - }, - "seccomp, allow any container annotation": { - pspAnnotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny, - }, - podAnnotations: map[string]string{ - kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo", - }, - shouldPassAdmit: true, - shouldPassValidate: true, - }, - "seccomp, allow specific pod annotation failure": { - pspAnnotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: "foo", - }, - podAnnotations: map[string]string{ - kapi.SeccompPodAnnotationKey: "bar", - }, - shouldPassAdmit: false, - shouldPassValidate: 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", - }, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "seccomp, allow specific pod annotation pass": { - pspAnnotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: "foo", - }, - podAnnotations: map[string]string{ - kapi.SeccompPodAnnotationKey: "foo", - }, - shouldPassAdmit: true, - shouldPassValidate: 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", - }, - shouldPassAdmit: true, - shouldPassValidate: true, - }, - } - for k, v := range tests { - psp := restrictivePSP() - psp.Annotations = v.pspAnnotations - pod := &kapi.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: v.podAnnotations, - }, - Spec: kapi.PodSpec{ - Containers: []kapi.Container{ - {Name: containerName}, - }, - }, - } - testPSPAdmit(k, []*policy.PodSecurityPolicy{psp}, pod, v.shouldPassAdmit, v.shouldPassValidate, psp.Name, t) - } -} - -func TestAdmitPrivileged(t *testing.T) { - createPodWithPriv := func(priv bool) *kapi.Pod { - pod := goodPod() - pod.Spec.Containers[0].SecurityContext.Privileged = &priv - return pod - } - - nonPrivilegedPSP := restrictivePSP() - nonPrivilegedPSP.Name = "non-priv" - nonPrivilegedPSP.Spec.Privileged = false - - privilegedPSP := restrictivePSP() - privilegedPSP.Name = "priv" - privilegedPSP.Spec.Privileged = true - - trueValue := true - falseValue := false - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedPriv *bool - expectedPSP string - }{ - "pod with priv=nil allowed under non priv PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{nonPrivilegedPSP}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPriv: nil, - expectedPSP: nonPrivilegedPSP.Name, - }, - "pod with priv=nil allowed under priv PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{privilegedPSP}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPriv: nil, - expectedPSP: privilegedPSP.Name, - }, - "pod with priv=false allowed under non priv PSP": { - pod: createPodWithPriv(false), - psps: []*policy.PodSecurityPolicy{nonPrivilegedPSP}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPriv: &falseValue, - expectedPSP: nonPrivilegedPSP.Name, - }, - "pod with priv=false allowed under priv PSP": { - pod: createPodWithPriv(false), - psps: []*policy.PodSecurityPolicy{privilegedPSP}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPriv: &falseValue, - expectedPSP: privilegedPSP.Name, - }, - "pod with priv=true denied by non priv PSP": { - pod: createPodWithPriv(true), - psps: []*policy.PodSecurityPolicy{nonPrivilegedPSP}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with priv=true allowed by priv PSP": { - pod: createPodWithPriv(true), - psps: []*policy.PodSecurityPolicy{nonPrivilegedPSP, privilegedPSP}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPriv: &trueValue, - expectedPSP: privilegedPSP.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - priv := v.pod.Spec.Containers[0].SecurityContext.Privileged - if (priv == nil) != (v.expectedPriv == nil) { - t.Errorf("%s expected privileged to be %v, got %v", k, v.expectedPriv, priv) - } else if priv != nil && *priv != *v.expectedPriv { - t.Errorf("%s expected privileged to be %v, got %v", k, *v.expectedPriv, *priv) - } - } - } -} - -func defaultPod(t *testing.T, pod *kapi.Pod) *kapi.Pod { - v1Pod := &v1.Pod{} - if err := legacyscheme.Scheme.Convert(pod, v1Pod, nil); err != nil { - t.Fatal(err) - } - legacyscheme.Scheme.Default(v1Pod) - apiPod := &kapi.Pod{} - if err := legacyscheme.Scheme.Convert(v1Pod, apiPod, nil); err != nil { - t.Fatal(err) - } - return apiPod -} - -func TestAdmitPreferNonmutating(t *testing.T) { - mutating1 := restrictivePSP() - mutating1.Name = "mutating1" - mutating1.Spec.RunAsUser.Ranges = []policy.IDRange{{Min: int64(1), Max: int64(1)}} - - mutating2 := restrictivePSP() - mutating2.Name = "mutating2" - mutating2.Spec.RunAsUser.Ranges = []policy.IDRange{{Min: int64(2), Max: int64(2)}} - - privilegedPSP := permissivePSP() - privilegedPSP.Name = "privileged" - - unprivilegedRunAsAnyPod := defaultPod(t, &kapi.Pod{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: kapi.PodSpec{ - ServiceAccountName: "default", - Containers: []kapi.Container{{Name: "mycontainer", Image: "myimage"}}, - }, - }) - changedPod := unprivilegedRunAsAnyPod.DeepCopy() - changedPod.Spec.Containers[0].Image = "myimage2" - - podWithSC := unprivilegedRunAsAnyPod.DeepCopy() - podWithSC.Annotations = map[string]string{psputil.ValidatedPSPAnnotation: privilegedPSP.Name} - changedPodWithSC := changedPod.DeepCopy() - changedPodWithSC.Annotations = map[string]string{psputil.ValidatedPSPAnnotation: privilegedPSP.Name} - - gcChangedPod := unprivilegedRunAsAnyPod.DeepCopy() - gcChangedPod.OwnerReferences = []metav1.OwnerReference{{Kind: "Foo", Name: "bar"}} - gcChangedPod.Finalizers = []string{"foo"} - - podWithAnnotation := unprivilegedRunAsAnyPod.DeepCopy() - podWithAnnotation.ObjectMeta.Annotations = map[string]string{ - // "mutating2" is lexicographically behind "mutating1", so "mutating1" should be - // chosen because it's the canonical PSP order. - psputil.ValidatedPSPAnnotation: mutating2.Name, - } - - tests := map[string]struct { - operation kadmission.Operation - pod *kapi.Pod - podBeforeUpdate *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassValidate bool - expectMutation bool - expectedContainerUser *int64 - expectedPSP string - }{ - "pod should not be mutated by allow-all strategies": { - operation: kadmission.Create, - pod: unprivilegedRunAsAnyPod.DeepCopy(), - psps: []*policy.PodSecurityPolicy{privilegedPSP}, - shouldPassValidate: true, - expectMutation: false, - expectedContainerUser: nil, - expectedPSP: privilegedPSP.Name, - }, - "pod should prefer non-mutating PSP on create": { - operation: kadmission.Create, - pod: unprivilegedRunAsAnyPod.DeepCopy(), - psps: []*policy.PodSecurityPolicy{mutating2, mutating1, privilegedPSP}, - shouldPassValidate: true, - expectMutation: false, - expectedContainerUser: nil, - expectedPSP: privilegedPSP.Name, - }, - "pod should use deterministic mutating PSP on create": { - operation: kadmission.Create, - pod: unprivilegedRunAsAnyPod.DeepCopy(), - psps: []*policy.PodSecurityPolicy{mutating2, mutating1}, - shouldPassValidate: true, - expectMutation: true, - expectedContainerUser: &mutating1.Spec.RunAsUser.Ranges[0].Min, - expectedPSP: mutating1.Name, - }, - "pod should use deterministic mutating PSP on create even if ValidatedPSPAnnotation is set": { - operation: kadmission.Create, - pod: podWithAnnotation, - psps: []*policy.PodSecurityPolicy{mutating2, mutating1}, - shouldPassValidate: true, - expectMutation: true, - expectedContainerUser: &mutating1.Spec.RunAsUser.Ranges[0].Min, - expectedPSP: mutating1.Name, - }, - "pod should prefer non-mutating PSP on update": { - operation: kadmission.Update, - pod: changedPodWithSC.DeepCopy(), - podBeforeUpdate: podWithSC.DeepCopy(), - psps: []*policy.PodSecurityPolicy{mutating2, mutating1, privilegedPSP}, - shouldPassValidate: true, - expectMutation: false, - expectedContainerUser: nil, - expectedPSP: privilegedPSP.Name, - }, - "pod should not mutate on update, but fail validation": { - operation: kadmission.Update, - pod: changedPod.DeepCopy(), - podBeforeUpdate: unprivilegedRunAsAnyPod.DeepCopy(), - psps: []*policy.PodSecurityPolicy{mutating2, mutating1}, - shouldPassValidate: false, - expectMutation: false, - expectedContainerUser: nil, - expectedPSP: "", - }, - "pod should be allowed if completely unchanged on update": { - operation: kadmission.Update, - pod: unprivilegedRunAsAnyPod.DeepCopy(), - podBeforeUpdate: unprivilegedRunAsAnyPod.DeepCopy(), - psps: []*policy.PodSecurityPolicy{mutating2, mutating1}, - shouldPassValidate: true, - expectMutation: false, - expectedContainerUser: nil, - expectedPSP: "", - }, - "pod should be allowed if unchanged on update except finalizers,ownerrefs": { - operation: kadmission.Update, - pod: gcChangedPod.DeepCopy(), - podBeforeUpdate: unprivilegedRunAsAnyPod.DeepCopy(), - psps: []*policy.PodSecurityPolicy{mutating2, mutating1}, - shouldPassValidate: true, - expectMutation: false, - expectedContainerUser: nil, - expectedPSP: "", - }, - } - - for k, v := range tests { - testPSPAdmitAdvanced(k, v.operation, v.psps, nil, &user.DefaultInfo{}, v.pod, v.podBeforeUpdate, true, v.shouldPassValidate, v.expectMutation, v.expectedPSP, t) - - actualPodUser := (*int64)(nil) - if v.pod.Spec.SecurityContext != nil { - actualPodUser = v.pod.Spec.SecurityContext.RunAsUser - } - if actualPodUser != nil { - t.Errorf("%s expected pod user nil, got %v", k, *actualPodUser) - } - - actualContainerUser := (*int64)(nil) - if v.pod.Spec.Containers[0].SecurityContext != nil { - actualContainerUser = v.pod.Spec.Containers[0].SecurityContext.RunAsUser - } - if (actualContainerUser == nil) != (v.expectedContainerUser == nil) { - t.Errorf("%s expected container user %v, got %v", k, v.expectedContainerUser, actualContainerUser) - } else if actualContainerUser != nil && *actualContainerUser != *v.expectedContainerUser { - t.Errorf("%s expected container user %v, got %v", k, *v.expectedContainerUser, *actualContainerUser) - } - } -} - -func TestFailClosedOnInvalidPod(t *testing.T) { - plugin := NewTestAdmission(nil, nil) - pod := &v1.Pod{} - attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{}) - - err := plugin.Admit(context.TODO(), attrs, nil) - if err == nil { - t.Fatalf("expected versioned pod object to fail mutating admission") - } - if !strings.Contains(err.Error(), "unexpected type") { - t.Errorf("expected type error on Admit but got: %v", err) - } - - err = plugin.Validate(context.TODO(), attrs, nil) - if err == nil { - t.Fatalf("expected versioned pod object to fail validating admission") - } - if !strings.Contains(err.Error(), "unexpected type") { - t.Errorf("expected type error on Validate but got: %v", err) - } -} - -func TestAdmitCaps(t *testing.T) { - createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod { - pod := goodPod() - pod.Spec.Containers[0].SecurityContext.Capabilities = caps - return pod - } - - restricted := restrictivePSP() - - allowsFooInAllowed := restrictivePSP() - allowsFooInAllowed.Name = "allowCapInAllowed" - allowsFooInAllowed.Spec.AllowedCapabilities = []v1.Capability{"foo"} - - allowsFooInRequired := restrictivePSP() - allowsFooInRequired.Name = "allowCapInRequired" - allowsFooInRequired.Spec.DefaultAddCapabilities = []v1.Capability{"foo"} - - requiresFooToBeDropped := restrictivePSP() - requiresFooToBeDropped.Name = "requireDrop" - requiresFooToBeDropped.Spec.RequiredDropCapabilities = []v1.Capability{"foo"} - - allowAllInAllowed := restrictivePSP() - allowAllInAllowed.Name = "allowAllCapsInAllowed" - allowAllInAllowed.Spec.AllowedCapabilities = []v1.Capability{policy.AllowAllCapabilities} - - tc := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedCapabilities *kapi.Capabilities - expectedPSP string - }{ - // UC 1: if a PSP does not define allowed or required caps then a pod requesting a cap - // should be rejected. - "should reject cap add when not allowed or required": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*policy.PodSecurityPolicy{restricted}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - // UC 2: if a PSP allows a cap in the allowed field it should accept the pod request - // to add the cap. - "should accept cap add when in allowed": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*policy.PodSecurityPolicy{restricted, allowsFooInAllowed}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: allowsFooInAllowed.Name, - }, - // UC 3: if a PSP requires a cap then it should accept the pod request - // to add the cap. - "should accept cap add when in required": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*policy.PodSecurityPolicy{restricted, allowsFooInRequired}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: allowsFooInRequired.Name, - }, - // UC 4: if a PSP requires a cap to be dropped then it should fail both - // in the verification of adds and verification of drops - "should reject cap add when requested cap is required to be dropped": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*policy.PodSecurityPolicy{restricted, requiresFooToBeDropped}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - // UC 5: if a PSP requires a cap to be dropped it should accept - // a manual request to drop the cap. - "should accept cap drop when cap is required to be dropped": { - pod: createPodWithCaps(&kapi.Capabilities{Drop: []kapi.Capability{"foo"}}), - psps: []*policy.PodSecurityPolicy{requiresFooToBeDropped}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: requiresFooToBeDropped.Name, - }, - // UC 6: required add is defaulted - "required add is defaulted": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{allowsFooInRequired}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedCapabilities: &kapi.Capabilities{ - Add: []kapi.Capability{"foo"}, - }, - expectedPSP: allowsFooInRequired.Name, - }, - // UC 7: required drop is defaulted - "required drop is defaulted": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{requiresFooToBeDropped}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedCapabilities: &kapi.Capabilities{ - Drop: []kapi.Capability{"foo"}, - }, - expectedPSP: requiresFooToBeDropped.Name, - }, - // UC 8: using '*' in allowed caps - "should accept cap add when all caps are allowed": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*policy.PodSecurityPolicy{restricted, allowAllInAllowed}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: allowAllInAllowed.Name, - }, - } - - for k, v := range tc { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.expectedCapabilities != nil { - if !reflect.DeepEqual(v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) { - t.Errorf("%s resulted in caps that were not expected - expected: %v, received: %v", k, v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) - } - } - } - - for k, v := range tc { - useInitContainers(v.pod) - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.expectedCapabilities != nil { - if !reflect.DeepEqual(v.expectedCapabilities, v.pod.Spec.InitContainers[0].SecurityContext.Capabilities) { - t.Errorf("%s resulted in caps that were not expected - expected: %v, received: %v", k, v.expectedCapabilities, v.pod.Spec.InitContainers[0].SecurityContext.Capabilities) - } - } - } - -} - -func TestAdmitVolumes(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() - - val := reflect.ValueOf(kapi.VolumeSource{}) - - for i := 0; i < val.NumField(); i++ { - // reflectively create the volume source - fieldVal := val.Type().Field(i) - - volumeSource := kapi.VolumeSource{} - volumeSourceVolume := reflect.New(fieldVal.Type.Elem()) - - reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume) - volume := kapi.Volume{VolumeSource: volumeSource} - - // sanity check before moving on - fsType, err := psputil.GetVolumeFSType(volume) - if err != nil { - t.Errorf("error getting FSType for %s: %s", fieldVal.Name, err.Error()) - continue - } - - // add the volume to the pod - pod := goodPod() - pod.Spec.Volumes = []kapi.Volume{volume} - - // create a PSP that allows no volumes - psp := restrictivePSP() - - // expect a denial for this PSP - testPSPAdmit(fmt.Sprintf("%s denial", string(fsType)), []*policy.PodSecurityPolicy{psp}, pod, false, false, "", t) - - // also expect a denial for this PSP if it's an init container - useInitContainers(pod) - testPSPAdmit(fmt.Sprintf("%s denial", string(fsType)), []*policy.PodSecurityPolicy{psp}, pod, false, false, "", t) - - // now add the fstype directly to the psp and it should validate - psp.Spec.Volumes = []policy.FSType{fsType} - testPSPAdmit(fmt.Sprintf("%s direct accept", string(fsType)), []*policy.PodSecurityPolicy{psp}, pod, true, true, psp.Name, t) - - // now change the psp to allow any volumes and the pod should still validate - psp.Spec.Volumes = []policy.FSType{policy.All} - testPSPAdmit(fmt.Sprintf("%s wildcard accept", string(fsType)), []*policy.PodSecurityPolicy{psp}, pod, true, true, psp.Name, t) - } -} - -func TestAdmitHostNetwork(t *testing.T) { - createPodWithHostNetwork := func(hostNetwork bool) *kapi.Pod { - pod := goodPod() - pod.Spec.SecurityContext.HostNetwork = hostNetwork - return pod - } - - noHostNetwork := restrictivePSP() - noHostNetwork.Name = "no-hostnetwork" - noHostNetwork.Spec.HostNetwork = false - - hostNetwork := restrictivePSP() - hostNetwork.Name = "hostnetwork" - hostNetwork.Spec.HostNetwork = true - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedHostNetwork bool - expectedPSP string - }{ - "pod without hostnetwork request allowed under noHostNetwork PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{noHostNetwork}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostNetwork: false, - expectedPSP: noHostNetwork.Name, - }, - "pod without hostnetwork request allowed under hostNetwork PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{hostNetwork}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostNetwork: false, - expectedPSP: hostNetwork.Name, - }, - "pod with hostnetwork request denied by noHostNetwork PSP": { - pod: createPodWithHostNetwork(true), - psps: []*policy.PodSecurityPolicy{noHostNetwork}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with hostnetwork request allowed by hostNetwork PSP": { - pod: createPodWithHostNetwork(true), - psps: []*policy.PodSecurityPolicy{noHostNetwork, hostNetwork}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostNetwork: true, - expectedPSP: hostNetwork.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if v.pod.Spec.SecurityContext.HostNetwork != v.expectedHostNetwork { - t.Errorf("%s expected hostNetwork to be %t", k, v.expectedHostNetwork) - } - } - } - - // test again with init containers - for k, v := range tests { - useInitContainers(v.pod) - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if v.pod.Spec.SecurityContext.HostNetwork != v.expectedHostNetwork { - t.Errorf("%s expected hostNetwork to be %t", k, v.expectedHostNetwork) - } - } - } -} - -func TestAdmitHostPorts(t *testing.T) { - createPodWithHostPorts := func(port int32) *kapi.Pod { - pod := goodPod() - pod.Spec.Containers[0].Ports = []kapi.ContainerPort{ - {HostPort: port}, - } - return pod - } - - noHostPorts := restrictivePSP() - noHostPorts.Name = "noHostPorts" - - hostPorts := restrictivePSP() - hostPorts.Name = "hostPorts" - hostPorts.Spec.HostPorts = []policy.HostPortRange{ - {Min: 1, Max: 10}, - } - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedPSP string - }{ - "host port out of range": { - pod: createPodWithHostPorts(11), - psps: []*policy.PodSecurityPolicy{hostPorts}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "host port in range": { - pod: createPodWithHostPorts(5), - psps: []*policy.PodSecurityPolicy{hostPorts}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: hostPorts.Name, - }, - "no host ports with range": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{hostPorts}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: hostPorts.Name, - }, - "no host ports without range": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{noHostPorts}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: noHostPorts.Name, - }, - "host ports without range": { - pod: createPodWithHostPorts(5), - psps: []*policy.PodSecurityPolicy{noHostPorts}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - } - - for i := 0; i < 2; i++ { - for k, v := range tests { - v.pod.Spec.Containers, v.pod.Spec.InitContainers = v.pod.Spec.InitContainers, v.pod.Spec.Containers - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - } - } -} - -func TestAdmitHostPID(t *testing.T) { - createPodWithHostPID := func(hostPID bool) *kapi.Pod { - pod := goodPod() - pod.Spec.SecurityContext.HostPID = hostPID - return pod - } - - noHostPID := restrictivePSP() - noHostPID.Name = "no-hostpid" - noHostPID.Spec.HostPID = false - - hostPID := restrictivePSP() - hostPID.Name = "hostpid" - hostPID.Spec.HostPID = true - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedHostPID bool - expectedPSP string - }{ - "pod without hostpid request allowed under noHostPID PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{noHostPID}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostPID: false, - expectedPSP: noHostPID.Name, - }, - "pod without hostpid request allowed under hostPID PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{hostPID}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostPID: false, - expectedPSP: hostPID.Name, - }, - "pod with hostpid request denied by noHostPID PSP": { - pod: createPodWithHostPID(true), - psps: []*policy.PodSecurityPolicy{noHostPID}, - shouldPassAdmit: false, - }, - "pod with hostpid request allowed by hostPID PSP": { - pod: createPodWithHostPID(true), - psps: []*policy.PodSecurityPolicy{noHostPID, hostPID}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostPID: true, - expectedPSP: hostPID.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if v.pod.Spec.SecurityContext.HostPID != v.expectedHostPID { - t.Errorf("%s expected hostPID to be %t", k, v.expectedHostPID) - } - } - } -} - -func TestAdmitHostIPC(t *testing.T) { - createPodWithHostIPC := func(hostIPC bool) *kapi.Pod { - pod := goodPod() - pod.Spec.SecurityContext.HostIPC = hostIPC - return pod - } - - noHostIPC := restrictivePSP() - noHostIPC.Name = "no-hostIPC" - noHostIPC.Spec.HostIPC = false - - hostIPC := restrictivePSP() - hostIPC.Name = "hostIPC" - hostIPC.Spec.HostIPC = true - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedHostIPC bool - expectedPSP string - }{ - "pod without hostIPC request allowed under noHostIPC PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{noHostIPC}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostIPC: false, - expectedPSP: noHostIPC.Name, - }, - "pod without hostIPC request allowed under hostIPC PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{hostIPC}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostIPC: false, - expectedPSP: hostIPC.Name, - }, - "pod with hostIPC request denied by noHostIPC PSP": { - pod: createPodWithHostIPC(true), - psps: []*policy.PodSecurityPolicy{noHostIPC}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with hostIPC request allowed by hostIPC PSP": { - pod: createPodWithHostIPC(true), - psps: []*policy.PodSecurityPolicy{noHostIPC, hostIPC}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedHostIPC: true, - expectedPSP: hostIPC.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if v.pod.Spec.SecurityContext.HostIPC != v.expectedHostIPC { - t.Errorf("%s expected hostIPC to be %t", k, v.expectedHostIPC) - } - } - } -} - -func createPodWithSecurityContexts(podSC *kapi.PodSecurityContext, containerSC *kapi.SecurityContext) *kapi.Pod { - pod := goodPod() - pod.Spec.SecurityContext = podSC - pod.Spec.Containers[0].SecurityContext = containerSC - return pod -} - -func TestAdmitSELinux(t *testing.T) { - runAsAny := permissivePSP() - runAsAny.Name = "runAsAny" - runAsAny.Spec.SELinux.Rule = policy.SELinuxStrategyRunAsAny - runAsAny.Spec.SELinux.SELinuxOptions = nil - - mustRunAs := permissivePSP() - mustRunAs.Name = "mustRunAs" - mustRunAs.Spec.SELinux.Rule = policy.SELinuxStrategyMustRunAs - mustRunAs.Spec.SELinux.SELinuxOptions = &v1.SELinuxOptions{} - mustRunAs.Spec.SELinux.SELinuxOptions.Level = "level" - mustRunAs.Spec.SELinux.SELinuxOptions.Role = "role" - mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type" - mustRunAs.Spec.SELinux.SELinuxOptions.User = "user" - - getInternalSEOptions := func(policy *policy.PodSecurityPolicy) *kapi.SELinuxOptions { - opt := kapi.SELinuxOptions{} - k8s_api_v1.Convert_v1_SELinuxOptions_To_core_SELinuxOptions(policy.Spec.SELinux.SELinuxOptions, &opt, nil) - return &opt - } - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedPodSC *kapi.PodSecurityContext - expectedContainerSC *kapi.SecurityContext - expectedPSP string - }{ - "runAsAny with no request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: nil, - expectedPSP: runAsAny.Name, - }, - "runAsAny with empty pod request": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{}, - expectedContainerSC: nil, - expectedPSP: runAsAny.Name, - }, - "runAsAny with empty container request": { - pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{}), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: &kapi.SecurityContext{}, - expectedPSP: runAsAny.Name, - }, - - "runAsAny with pod request": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, - expectedContainerSC: nil, - expectedPSP: runAsAny.Name, - }, - "runAsAny with container request": { - pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, - expectedPSP: runAsAny.Name, - }, - "runAsAny with pod and container request": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, - expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, - expectedPSP: runAsAny.Name, - }, - - "mustRunAs with bad pod request": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "mustRunAs with bad container request": { - pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "mustRunAs with no request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: getInternalSEOptions(mustRunAs)}, - expectedContainerSC: nil, - expectedPSP: mustRunAs.Name, - }, - "mustRunAs with good pod request": { - pod: createPodWithSecurityContexts( - &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}}, - nil, - ), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: getInternalSEOptions(mustRunAs)}, - expectedContainerSC: nil, - expectedPSP: mustRunAs.Name, - }, - "mustRunAs with good container request": { - pod: createPodWithSecurityContexts( - &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}}, - nil, - ), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: getInternalSEOptions(mustRunAs)}, - expectedContainerSC: nil, - expectedPSP: mustRunAs.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { - t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) - } - if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) { - t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext)) - } - } - } -} - -func TestAdmitAppArmor(t *testing.T) { - createPodWithAppArmor := func(profile string) *kapi.Pod { - pod := goodPod() - apparmor.SetProfileNameFromPodAnnotations(pod.Annotations, defaultContainerName, profile) - return pod - } - - unconstrainedPSP := restrictivePSP() - defaultedPSP := restrictivePSP() - defaultedPSP.Annotations = map[string]string{ - v1.AppArmorBetaDefaultProfileAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - } - appArmorPSP := restrictivePSP() - appArmorPSP.Annotations = map[string]string{ - v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - } - appArmorDefaultPSP := restrictivePSP() - appArmorDefaultPSP.Annotations = map[string]string{ - v1.AppArmorBetaDefaultProfileAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault + "," + v1.AppArmorBetaProfileNamePrefix + "foo", - } - - tests := map[string]struct { - pod *kapi.Pod - psp *policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedProfile string - }{ - "unconstrained with no profile": { - pod: goodPod(), - psp: unconstrainedPSP, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedProfile: "", - }, - "unconstrained with profile": { - pod: createPodWithAppArmor(v1.AppArmorBetaProfileRuntimeDefault), - psp: unconstrainedPSP, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedProfile: v1.AppArmorBetaProfileRuntimeDefault, - }, - "unconstrained with default profile": { - pod: goodPod(), - psp: defaultedPSP, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedProfile: v1.AppArmorBetaProfileRuntimeDefault, - }, - "AppArmor enforced with no profile": { - pod: goodPod(), - psp: appArmorPSP, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "AppArmor enforced with default profile": { - pod: goodPod(), - psp: appArmorDefaultPSP, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedProfile: v1.AppArmorBetaProfileRuntimeDefault, - }, - "AppArmor enforced with good profile": { - pod: createPodWithAppArmor(v1.AppArmorBetaProfileNamePrefix + "foo"), - psp: appArmorDefaultPSP, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedProfile: v1.AppArmorBetaProfileNamePrefix + "foo", - }, - "AppArmor enforced with local profile": { - pod: createPodWithAppArmor(v1.AppArmorBetaProfileNamePrefix + "bar"), - psp: appArmorPSP, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - } - - for k, v := range tests { - testPSPAdmit(k, []*policy.PodSecurityPolicy{v.psp}, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.psp.Name, t) - - if v.shouldPassAdmit { - assert.Equal(t, v.expectedProfile, apparmor.GetProfileNameFromPodAnnotations(v.pod.Annotations, defaultContainerName), k) - } - } -} - -func TestAdmitRunAsUser(t *testing.T) { - podSC := func(user *int64) *kapi.PodSecurityContext { - return &kapi.PodSecurityContext{RunAsUser: user} - } - containerSC := func(user *int64) *kapi.SecurityContext { - return &kapi.SecurityContext{RunAsUser: user} - } - - runAsAny := permissivePSP() - runAsAny.Name = "runAsAny" - runAsAny.Spec.RunAsUser.Rule = policy.RunAsUserStrategyRunAsAny - - mustRunAs := permissivePSP() - mustRunAs.Name = "mustRunAs" - mustRunAs.Spec.RunAsUser.Rule = policy.RunAsUserStrategyMustRunAs - mustRunAs.Spec.RunAsUser.Ranges = []policy.IDRange{ - {Min: int64(999), Max: int64(1000)}, - } - - runAsNonRoot := permissivePSP() - runAsNonRoot.Name = "runAsNonRoot" - runAsNonRoot.Spec.RunAsUser.Rule = policy.RunAsUserStrategyMustRunAsNonRoot - - trueValue := true - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedPodSC *kapi.PodSecurityContext - expectedContainerSC *kapi.SecurityContext - expectedPSP string - }{ - "runAsAny no pod request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: nil, - expectedPSP: runAsAny.Name, - }, - "runAsAny pod request": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(1)), nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: podSC(utilpointer.Int64Ptr(1)), - expectedContainerSC: nil, - expectedPSP: runAsAny.Name, - }, - "runAsAny container request": { - pod: createPodWithSecurityContexts(nil, containerSC(utilpointer.Int64Ptr(1))), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: containerSC(utilpointer.Int64Ptr(1)), - expectedPSP: runAsAny.Name, - }, - - "mustRunAs pod request out of range": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(1)), nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "mustRunAs container request out of range": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(999)), containerSC(utilpointer.Int64Ptr(1))), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - - "mustRunAs pod request in range": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(999)), nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: podSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), - expectedContainerSC: nil, - expectedPSP: mustRunAs.Name, - }, - "mustRunAs container request in range": { - pod: createPodWithSecurityContexts(nil, containerSC(utilpointer.Int64Ptr(999))), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), - expectedPSP: mustRunAs.Name, - }, - "mustRunAs pod and container request in range": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(999)), containerSC(utilpointer.Int64Ptr(1000))), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: podSC(utilpointer.Int64Ptr(999)), - expectedContainerSC: containerSC(utilpointer.Int64Ptr(1000)), - expectedPSP: mustRunAs.Name, - }, - "mustRunAs no request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), - expectedPSP: mustRunAs.Name, - }, - - "runAsNonRoot no request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*policy.PodSecurityPolicy{runAsNonRoot}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedContainerSC: &kapi.SecurityContext{RunAsNonRoot: &trueValue}, - expectedPSP: runAsNonRoot.Name, - }, - "runAsNonRoot pod request root": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(0)), nil), - psps: []*policy.PodSecurityPolicy{runAsNonRoot}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "runAsNonRoot pod request non-root": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(1)), nil), - psps: []*policy.PodSecurityPolicy{runAsNonRoot}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: podSC(utilpointer.Int64Ptr(1)), - expectedPSP: runAsNonRoot.Name, - }, - "runAsNonRoot container request root": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(1)), containerSC(utilpointer.Int64Ptr(0))), - psps: []*policy.PodSecurityPolicy{runAsNonRoot}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "runAsNonRoot container request non-root": { - pod: createPodWithSecurityContexts(podSC(utilpointer.Int64Ptr(1)), containerSC(utilpointer.Int64Ptr(2))), - psps: []*policy.PodSecurityPolicy{runAsNonRoot}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: podSC(utilpointer.Int64Ptr(1)), - expectedContainerSC: containerSC(utilpointer.Int64Ptr(2)), - expectedPSP: runAsNonRoot.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { - t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) - } - if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) { - t.Errorf("%s unexpected container sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext)) - } - } - } -} - -func TestAdmitSupplementalGroups(t *testing.T) { - podSC := func(group int64) *kapi.PodSecurityContext { - return &kapi.PodSecurityContext{SupplementalGroups: []int64{group}} - } - - runAsAny := permissivePSP() - runAsAny.Name = "runAsAny" - runAsAny.Spec.SupplementalGroups.Rule = policy.SupplementalGroupsStrategyRunAsAny - - mustRunAs := permissivePSP() - mustRunAs.Name = "mustRunAs" - mustRunAs.Spec.SupplementalGroups.Rule = policy.SupplementalGroupsStrategyMustRunAs - mustRunAs.Spec.SupplementalGroups.Ranges = []policy.IDRange{{Min: int64(999), Max: int64(1000)}} - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedPodSC *kapi.PodSecurityContext - expectedPSP string - }{ - "runAsAny no pod request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: nil, - expectedPSP: runAsAny.Name, - }, - "runAsAny empty pod request": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{}, - expectedPSP: runAsAny.Name, - }, - "runAsAny empty pod request empty supplemental groups": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SupplementalGroups: []int64{}}, nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{}}, - expectedPSP: runAsAny.Name, - }, - "runAsAny pod request": { - pod: createPodWithSecurityContexts(podSC(1), nil), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}}, - expectedPSP: runAsAny.Name, - }, - "mustRunAs no pod request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min), - expectedPSP: mustRunAs.Name, - }, - "mustRunAs bad pod request": { - pod: createPodWithSecurityContexts(podSC(1), nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "mustRunAs good pod request": { - pod: createPodWithSecurityContexts(podSC(999), nil), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPodSC: podSC(999), - expectedPSP: mustRunAs.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { - t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) - } - } - } -} - -func TestAdmitFSGroup(t *testing.T) { - createPodWithFSGroup := func(group int64) *kapi.Pod { - pod := goodPod() - // doesn't matter if we set it here or on the container, the - // admission controller uses DetermineEffectiveSC to get the defaulting - // behavior so it can validate what will be applied at runtime - pod.Spec.SecurityContext.FSGroup = utilpointer.Int64Ptr(group) - return pod - } - - runAsAny := restrictivePSP() - runAsAny.Name = "runAsAny" - runAsAny.Spec.FSGroup.Rule = policy.FSGroupStrategyRunAsAny - - mustRunAs := restrictivePSP() - mustRunAs.Name = "mustRunAs" - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedFSGroup *int64 - expectedPSP string - }{ - "runAsAny no pod request": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedFSGroup: nil, - expectedPSP: runAsAny.Name, - }, - "runAsAny pod request": { - pod: createPodWithFSGroup(1), - psps: []*policy.PodSecurityPolicy{runAsAny}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedFSGroup: utilpointer.Int64Ptr(1), - expectedPSP: runAsAny.Name, - }, - "mustRunAs no pod request": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedFSGroup: &mustRunAs.Spec.SupplementalGroups.Ranges[0].Min, - expectedPSP: mustRunAs.Name, - }, - "mustRunAs bad pod request": { - pod: createPodWithFSGroup(1), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "mustRunAs good pod request": { - pod: createPodWithFSGroup(999), - psps: []*policy.PodSecurityPolicy{mustRunAs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedFSGroup: utilpointer.Int64Ptr(999), - expectedPSP: mustRunAs.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if v.pod.Spec.SecurityContext.FSGroup == nil && v.expectedFSGroup == nil { - // ok, don't need to worry about identifying specific diffs - continue - } - if v.pod.Spec.SecurityContext.FSGroup == nil && v.expectedFSGroup != nil { - t.Errorf("%s expected FSGroup to be: %v but found nil", k, *v.expectedFSGroup) - continue - } - if v.pod.Spec.SecurityContext.FSGroup != nil && v.expectedFSGroup == nil { - t.Errorf("%s expected FSGroup to be nil but found: %v", k, *v.pod.Spec.SecurityContext.FSGroup) - continue - } - if *v.expectedFSGroup != *v.pod.Spec.SecurityContext.FSGroup { - t.Errorf("%s expected FSGroup to be: %v but found %v", k, *v.expectedFSGroup, *v.pod.Spec.SecurityContext.FSGroup) - } - } - } -} - -func TestAdmitReadOnlyRootFilesystem(t *testing.T) { - createPodWithRORFS := func(rorfs bool) *kapi.Pod { - pod := goodPod() - pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &rorfs - return pod - } - - noRORFS := restrictivePSP() - noRORFS.Name = "no-rorfs" - noRORFS.Spec.ReadOnlyRootFilesystem = false - - rorfs := restrictivePSP() - rorfs.Name = "rorfs" - rorfs.Spec.ReadOnlyRootFilesystem = true - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedRORFS bool - expectedPSP string - }{ - "no-rorfs allows pod request with rorfs": { - pod: createPodWithRORFS(true), - psps: []*policy.PodSecurityPolicy{noRORFS}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedRORFS: true, - expectedPSP: noRORFS.Name, - }, - "no-rorfs allows pod request without rorfs": { - pod: createPodWithRORFS(false), - psps: []*policy.PodSecurityPolicy{noRORFS}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedRORFS: false, - expectedPSP: noRORFS.Name, - }, - "rorfs rejects pod request without rorfs": { - pod: createPodWithRORFS(false), - psps: []*policy.PodSecurityPolicy{rorfs}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "rorfs defaults nil pod request": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{rorfs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedRORFS: true, - expectedPSP: rorfs.Name, - }, - "rorfs accepts pod request with rorfs": { - pod: createPodWithRORFS(true), - psps: []*policy.PodSecurityPolicy{rorfs}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedRORFS: true, - expectedPSP: rorfs.Name, - }, - } - - for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem == nil || - *v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem != v.expectedRORFS { - t.Errorf("%s expected ReadOnlyRootFilesystem to be %t but found %#v", k, v.expectedRORFS, v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem) - } - } - } -} - -func TestAdmitSysctls(t *testing.T) { - podWithSysctls := func(safeSysctls []string, unsafeSysctls []string) *kapi.Pod { - pod := goodPod() - dummySysctls := func(names []string) []kapi.Sysctl { - sysctls := make([]kapi.Sysctl, len(names)) - for i, n := range names { - sysctls[i].Name = n - sysctls[i].Value = "dummy" - } - return sysctls - } - pod.Spec.SecurityContext = &kapi.PodSecurityContext{ - Sysctls: dummySysctls(append(safeSysctls, unsafeSysctls...)), - } - - return pod - } - - safeSysctls := restrictivePSP() - safeSysctls.Name = "no sysctls" - - noSysctls := restrictivePSP() - noSysctls.Name = "empty sysctls" - noSysctls.Spec.ForbiddenSysctls = []string{"*"} - - mixedSysctls := restrictivePSP() - mixedSysctls.Name = "wildcard sysctls" - mixedSysctls.Spec.ForbiddenSysctls = []string{"net.*"} - mixedSysctls.Spec.AllowedUnsafeSysctls = []string{"a.*", "b.*"} - - aUnsafeSysctl := restrictivePSP() - aUnsafeSysctl.Name = "a sysctl" - aUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"a"} - - bUnsafeSysctl := restrictivePSP() - bUnsafeSysctl.Name = "b sysctl" - bUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"b"} - - cUnsafeSysctl := restrictivePSP() - cUnsafeSysctl.Name = "c sysctl" - cUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"c"} - - catchallSysctls := restrictivePSP() - catchallSysctls.Name = "catchall sysctl" - catchallSysctls.Spec.AllowedUnsafeSysctls = []string{"*"} - - tests := map[string]struct { - pod *kapi.Pod - psps []*policy.PodSecurityPolicy - shouldPassAdmit bool - shouldPassValidate bool - expectedPSP string - }{ - "pod without any sysctls request allowed under safeSysctls PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{safeSysctls}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: safeSysctls.Name, - }, - "pod without any sysctls request allowed under noSysctls PSP": { - pod: goodPod(), - psps: []*policy.PodSecurityPolicy{noSysctls}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: noSysctls.Name, - }, - "pod with safe sysctls request allowed under safeSysctls PSP": { - pod: podWithSysctls([]string{"kernel.shm_rmid_forced", "net.ipv4.tcp_syncookies"}, []string{}), - psps: []*policy.PodSecurityPolicy{safeSysctls}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: safeSysctls.Name, - }, - "pod with unsafe sysctls request disallowed under noSysctls PSP": { - pod: podWithSysctls([]string{}, []string{"a", "b"}), - psps: []*policy.PodSecurityPolicy{noSysctls}, - shouldPassAdmit: false, - shouldPassValidate: false, - expectedPSP: noSysctls.Name, - }, - "pod with unsafe sysctls a, b request disallowed under aUnsafeSysctl SCC": { - pod: podWithSysctls([]string{}, []string{"a", "b"}), - psps: []*policy.PodSecurityPolicy{aUnsafeSysctl}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with unsafe sysctls b request disallowed under aUnsafeSysctl SCC": { - pod: podWithSysctls([]string{}, []string{"b"}), - psps: []*policy.PodSecurityPolicy{aUnsafeSysctl}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with unsafe sysctls a request allowed under aUnsafeSysctl SCC": { - pod: podWithSysctls([]string{}, []string{"a"}), - psps: []*policy.PodSecurityPolicy{aUnsafeSysctl}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: aUnsafeSysctl.Name, - }, - "pod with safe net sysctl request allowed under aUnsafeSysctl SCC": { - pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}), - psps: []*policy.PodSecurityPolicy{aUnsafeSysctl}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: aUnsafeSysctl.Name, - }, - "pod with safe sysctls request disallowed under noSysctls PSP": { - pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}), - psps: []*policy.PodSecurityPolicy{noSysctls}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with matching sysctls request allowed under mixedSysctls PSP": { - pod: podWithSysctls([]string{"kernel.shm_rmid_forced"}, []string{"a.b", "b.a"}), - psps: []*policy.PodSecurityPolicy{mixedSysctls}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: mixedSysctls.Name, - }, - "pod with not-matching unsafe sysctls request disallowed under mixedSysctls PSP": { - pod: podWithSysctls([]string{}, []string{"e"}), - psps: []*policy.PodSecurityPolicy{mixedSysctls}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with not-matching safe sysctls request disallowed under mixedSysctls PSP": { - pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}), - psps: []*policy.PodSecurityPolicy{mixedSysctls}, - shouldPassAdmit: false, - shouldPassValidate: false, - }, - "pod with sysctls request allowed under catchallSysctls PSP": { - pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{"f"}), - psps: []*policy.PodSecurityPolicy{catchallSysctls}, - shouldPassAdmit: true, - shouldPassValidate: true, - expectedPSP: catchallSysctls.Name, - }, - } - - for k, v := range tests { - origSysctl := v.pod.Spec.SecurityContext.Sysctls - - testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - - if v.shouldPassAdmit { - if !reflect.DeepEqual(v.pod.Spec.SecurityContext.Sysctls, origSysctl) { - t.Errorf("%s: wrong sysctls: expected=%v, got=%v", k, origSysctl, v.pod.Spec.SecurityContext.Sysctls) - } - } - } -} - -func testPSPAdmit(testCaseName string, psps []*policy.PodSecurityPolicy, pod *kapi.Pod, shouldPassAdmit, shouldPassValidate bool, expectedPSP string, t *testing.T) { - testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, nil, &user.DefaultInfo{}, pod, nil, shouldPassAdmit, shouldPassValidate, true, expectedPSP, t) -} - -// fakeAttributes decorate kadmission.Attributes. It's used to trace the added annotations. -type fakeAttributes struct { - kadmission.Attributes - annotations map[string]string -} - -func (f fakeAttributes) AddAnnotation(k, v string) error { - f.annotations[k] = v - return f.Attributes.AddAnnotation(k, v) -} - -func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*policy.PodSecurityPolicy, authz authorizer.Authorizer, userInfo user.Info, pod, oldPod *kapi.Pod, shouldPassAdmit, shouldPassValidate bool, canMutate bool, expectedPSP string, t *testing.T) { - originalPod := pod.DeepCopy() - plugin := NewTestAdmission(psps, authz) - - attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, "", kapi.Resource("pods").WithVersion("version"), "", op, nil, false, userInfo) - annotations := make(map[string]string) - attrs = &fakeAttributes{attrs, annotations} - err := admissiontesting.WithReinvocationTesting(t, plugin).Admit(context.TODO(), attrs, nil) - - if shouldPassAdmit && err != nil { - t.Errorf("%s: expected no errors on Admit but received %v", testCaseName, err) - } - - if shouldPassAdmit && err == nil { - if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP { - t.Errorf("%s: expected to be admitted under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation]) - } - - if !canMutate { - podWithoutPSPAnnotation := pod.DeepCopy() - delete(podWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation) - - originalPodWithoutPSPAnnotation := originalPod.DeepCopy() - delete(originalPodWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation) - - if !apiequality.Semantic.DeepEqual(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec) { - t.Errorf("%s: expected no mutation on Admit, got %s", testCaseName, diff.ObjectGoPrintSideBySide(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec)) - } - } - } - - if !shouldPassAdmit && err == nil { - t.Errorf("%s: expected errors on Admit but received none", testCaseName) - } - - err = plugin.Validate(context.TODO(), attrs, nil) - psp := "" - if shouldPassAdmit && op == kadmission.Create { - psp = expectedPSP - } - validateAuditAnnotation(t, testCaseName, annotations, "podsecuritypolicy.policy.k8s.io/admit-policy", psp) - if shouldPassValidate && err != nil { - t.Errorf("%s: expected no errors on Validate but received %v", testCaseName, err) - } else if !shouldPassValidate && err == nil { - t.Errorf("%s: expected errors on Validate but received none", testCaseName) - } - if shouldPassValidate { - validateAuditAnnotation(t, testCaseName, annotations, "podsecuritypolicy.policy.k8s.io/validate-policy", expectedPSP) - } else { - validateAuditAnnotation(t, testCaseName, annotations, "podsecuritypolicy.policy.k8s.io/validate-policy", "") - } -} - -func validateAuditAnnotation(t *testing.T, testCaseName string, annotations map[string]string, key, value string) { - if annotations[key] != value { - t.Errorf("%s: expected to have annotations[%s] set to %q, got %q", testCaseName, key, value, annotations[key]) - } -} - -func TestAssignSecurityContext(t *testing.T) { - // psp that will deny privileged container requests and has a default value for a field (uid) - psp := restrictivePSP() - provider, err := kpsp.NewSimpleProvider(psp, "namespace", kpsp.NewSimpleStrategyFactory()) - if err != nil { - t.Fatalf("failed to create provider: %v", err) - } - - createContainer := func(priv bool) kapi.Container { - return kapi.Container{ - SecurityContext: &kapi.SecurityContext{ - Privileged: &priv, - }, - } - } - - testCases := map[string]struct { - pod *kapi.Pod - shouldValidate bool - expectedUID *int64 - }{ - "pod and container SC is not changed when invalid": { - pod: &kapi.Pod{ - Spec: kapi.PodSpec{ - SecurityContext: &kapi.PodSecurityContext{}, - Containers: []kapi.Container{createContainer(true)}, - }, - }, - shouldValidate: false, - }, - "must validate all containers": { - pod: &kapi.Pod{ - Spec: kapi.PodSpec{ - // good container and bad container - SecurityContext: &kapi.PodSecurityContext{}, - Containers: []kapi.Container{createContainer(false), createContainer(true)}, - }, - }, - shouldValidate: false, - }, - "pod validates": { - pod: &kapi.Pod{ - Spec: kapi.PodSpec{ - SecurityContext: &kapi.PodSecurityContext{}, - Containers: []kapi.Container{createContainer(false)}, - }, - }, - shouldValidate: true, - }, - } - - for k, v := range testCases { - errs := assignSecurityContext(provider, v.pod) - if v.shouldValidate && len(errs) > 0 { - t.Errorf("%s expected to validate but received errors %v", k, errs) - continue - } - if !v.shouldValidate && len(errs) == 0 { - t.Errorf("%s expected validation errors but received none", k) - continue - } - } -} - -func TestCreateProvidersFromConstraints(t *testing.T) { - testCases := map[string]struct { - // use a generating function so we can test for non-mutation - psp func() *policy.PodSecurityPolicy - expectedErr string - }{ - "valid psp": { - psp: func() *policy.PodSecurityPolicy { - return &policy.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valid psp", - }, - Spec: policy.PodSecurityPolicySpec{ - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyRunAsAny, - }, - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyRunAsAny, - }, - RunAsGroup: &policy.RunAsGroupStrategyOptions{ - Rule: policy.RunAsGroupStrategyRunAsAny, - }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyRunAsAny, - }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyRunAsAny, - }, - }, - } - }, - }, - "bad psp strategy options": { - psp: func() *policy.PodSecurityPolicy { - return &policy.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bad psp user options", - }, - Spec: policy.PodSecurityPolicySpec{ - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyRunAsAny, - }, - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyMustRunAs, - }, - RunAsGroup: &policy.RunAsGroupStrategyOptions{ - Rule: policy.RunAsGroupStrategyRunAsAny, - }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyRunAsAny, - }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyRunAsAny, - }, - }, - } - }, - expectedErr: "MustRunAs requires at least one range", - }, - } - - for k, v := range testCases { - admit := &Plugin{ - Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), - strategyFactory: kpsp.NewSimpleStrategyFactory(), - } - - psp := v.psp() - _, errs := admit.createProvidersFromPolicies([]*policy.PodSecurityPolicy{psp}, "namespace") - - if !reflect.DeepEqual(psp, v.psp()) { - diff := diff.ObjectDiff(psp, v.psp()) - t.Errorf("%s createProvidersFromPolicies mutated policy. diff:\n%s", k, diff) - } - if len(v.expectedErr) > 0 && len(errs) != 1 { - t.Errorf("%s expected a single error '%s' but received %v", k, v.expectedErr, errs) - continue - } - if len(v.expectedErr) == 0 && len(errs) != 0 { - t.Errorf("%s did not expect an error but received %v", k, errs) - continue - } - - // check that we got the error we expected - if len(v.expectedErr) > 0 { - if !strings.Contains(errs[0].Error(), v.expectedErr) { - t.Errorf("%s expected error '%s' but received %v", k, v.expectedErr, errs[0]) - } - } - } -} - -func TestPolicyAuthorization(t *testing.T) { - policyWithName := func(name string) *policy.PodSecurityPolicy { - p := permissivePSP() - p.Name = name - return p - } - - tests := map[string]struct { - user user.Info - sa string - ns string - expectedPolicy string - inPolicies []*policy.PodSecurityPolicy - allowed map[string]map[string]map[string]bool - allowedGroup string - }{ - "policy allowed by user (extensions API Group)": { - user: &user.DefaultInfo{Name: "user"}, - sa: "sa", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - "user": { - "test": {"policy": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicy: "policy", - }, - "policy allowed by sa (extensions API Group)": { - user: &user.DefaultInfo{Name: "user"}, - sa: "sa", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicy: "policy", - }, - "policy allowed by user (policy API Group)": { - user: &user.DefaultInfo{Name: "user"}, - sa: "sa", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - "user": { - "test": {"policy": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicy: "policy", - allowedGroup: policy.GroupName, - }, - "policy allowed by sa (policy API Group)": { - user: &user.DefaultInfo{Name: "user"}, - sa: "sa", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicy: "policy", - allowedGroup: policy.GroupName, - }, - "no policies allowed": { - user: &user.DefaultInfo{Name: "user"}, - sa: "sa", - ns: "test", - allowed: map[string]map[string]map[string]bool{}, - inPolicies: []*policy.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicy: "", - }, - "multiple policies allowed": { - user: &user.DefaultInfo{Name: "user"}, - sa: "sa", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy1": true}, - "": {"policy4": true}, - "other": {"policy6": true}, - }, - "user": { - "test": {"policy2": true}, - "": {"policy5": true}, - "other": {"policy7": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{ - // Prefix to force checking these policies first. - policyWithName("a_policy1"), // not allowed in this namespace - policyWithName("a_policy2"), // not allowed in this namespace - policyWithName("policy2"), // allowed by sa - policyWithName("policy3"), // allowed by user - policyWithName("policy4"), // not allowed - policyWithName("policy5"), // allowed by sa at cluster level - policyWithName("policy6"), // allowed by user at cluster level - }, - expectedPolicy: "policy2", - }, - "policies are not allowed for nil user info": { - user: nil, - sa: "sa", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy1": true}, - }, - "user": { - "test": {"policy2": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{ - policyWithName("policy1"), - policyWithName("policy2"), - policyWithName("policy3"), - }, - // only the policies for the sa are allowed when user info is nil - expectedPolicy: "policy1", - }, - "policies are not allowed for nil sa info": { - user: &user.DefaultInfo{Name: "user"}, - sa: "", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy1": true}, - }, - "user": { - "test": {"policy2": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{ - policyWithName("policy1"), - policyWithName("policy2"), - policyWithName("policy3"), - }, - // only the policies for the user are allowed when sa info is nil - expectedPolicy: "policy2", - }, - "policies are not allowed for nil sa and user info": { - user: nil, - sa: "", - ns: "test", - allowed: map[string]map[string]map[string]bool{ - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy1": true}, - }, - "user": { - "test": {"policy2": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{ - policyWithName("policy1"), - policyWithName("policy2"), - policyWithName("policy3"), - }, - // no policies are allowed if sa and user are both nil - expectedPolicy: "", - }, - } - for k, v := range tests { - var ( - oldPod *kapi.Pod - shouldPass = v.expectedPolicy != "" - authz = &TestAuthorizer{usernameToNamespaceToAllowedPSPs: v.allowed, allowedAPIGroupName: v.allowedGroup} - canMutate = true - ) - pod := goodPod() - pod.Namespace = v.ns - pod.Spec.ServiceAccountName = v.sa - testPSPAdmitAdvanced(k, kadmission.Create, v.inPolicies, authz, v.user, - pod, oldPod, shouldPass, shouldPass, canMutate, v.expectedPolicy, t) - } -} - -func TestPolicyAuthorizationErrors(t *testing.T) { - policyWithName := func(name string) *policy.PodSecurityPolicy { - p := restrictivePSP() - p.Name = name - return p - } - - const ( - sa = "sa" - ns = "test" - userName = "user" - ) - - tests := map[string]struct { - inPolicies []*policy.PodSecurityPolicy - allowed map[string]map[string]map[string]bool - expectValidationErrs int - }{ - "policies not allowed": { - allowed: map[string]map[string]map[string]bool{}, - inPolicies: []*policy.PodSecurityPolicy{ - policyWithName("policy1"), - policyWithName("policy2"), - }, - expectValidationErrs: 0, - }, - "policy allowed by user": { - allowed: map[string]map[string]map[string]bool{ - "user": { - "test": {"policy1": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{ - policyWithName("policy1"), - policyWithName("policy2"), - }, - expectValidationErrs: 1, - }, - "policy allowed by service account": { - allowed: map[string]map[string]map[string]bool{ - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy2": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{ - policyWithName("policy1"), - policyWithName("policy2"), - }, - expectValidationErrs: 1, - }, - "multiple policies allowed": { - allowed: map[string]map[string]map[string]bool{ - "user": { - "test": {"policy1": true}, - }, - serviceaccount.MakeUsername("test", "sa"): { - "test": {"policy2": true}, - }, - }, - inPolicies: []*policy.PodSecurityPolicy{ - policyWithName("policy1"), - policyWithName("policy2"), - }, - expectValidationErrs: 2, - }, - } - for desc, tc := range tests { - t.Run(desc, func(t *testing.T) { - authz := &TestAuthorizer{usernameToNamespaceToAllowedPSPs: tc.allowed} - pod := goodPod() - pod.Namespace = ns - pod.Spec.ServiceAccountName = sa - pod.Spec.SecurityContext.HostPID = true - - plugin := NewTestAdmission(tc.inPolicies, authz) - attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), ns, "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{Name: userName}) - - allowedPod, _, validationErrs, err := plugin.computeSecurityContext(context.Background(), attrs, pod, true, "") - assert.Nil(t, allowedPod) - assert.NoError(t, err) - assert.Len(t, validationErrs, tc.expectValidationErrs) - }) - } -} - -func TestPreferValidatedPSP(t *testing.T) { - restrictivePSPWithName := func(name string) *policy.PodSecurityPolicy { - p := restrictivePSP() - p.Name = name - return p - } - - permissivePSPWithName := func(name string) *policy.PodSecurityPolicy { - p := permissivePSP() - p.Name = name - return p - } - - tests := map[string]struct { - inPolicies []*policy.PodSecurityPolicy - expectValidationErrs int - validatedPSPHint string - expectedPSP string - }{ - "no policy saved in annotations, PSPs are ordered lexicographically": { - inPolicies: []*policy.PodSecurityPolicy{ - restrictivePSPWithName("001restrictive"), - restrictivePSPWithName("002restrictive"), - permissivePSPWithName("002permissive"), - permissivePSPWithName("001permissive"), - permissivePSPWithName("003permissive"), - }, - expectValidationErrs: 0, - validatedPSPHint: "", - expectedPSP: "001permissive", - }, - "policy saved in annotations is preferred": { - inPolicies: []*policy.PodSecurityPolicy{ - restrictivePSPWithName("001restrictive"), - restrictivePSPWithName("002restrictive"), - permissivePSPWithName("001permissive"), - permissivePSPWithName("002permissive"), - permissivePSPWithName("003permissive"), - }, - expectValidationErrs: 0, - validatedPSPHint: "002permissive", - expectedPSP: "002permissive", - }, - "policy saved in annotations is invalid": { - inPolicies: []*policy.PodSecurityPolicy{ - restrictivePSPWithName("001restrictive"), - restrictivePSPWithName("002restrictive"), - }, - expectValidationErrs: 2, - validatedPSPHint: "foo", - expectedPSP: "", - }, - "policy saved in annotations is disallowed anymore": { - inPolicies: []*policy.PodSecurityPolicy{ - restrictivePSPWithName("001restrictive"), - restrictivePSPWithName("002restrictive"), - }, - expectValidationErrs: 2, - validatedPSPHint: "001restrictive", - expectedPSP: "", - }, - "policy saved in annotations is disallowed anymore, but find another one": { - inPolicies: []*policy.PodSecurityPolicy{ - restrictivePSPWithName("001restrictive"), - restrictivePSPWithName("002restrictive"), - permissivePSPWithName("002permissive"), - permissivePSPWithName("001permissive"), - }, - expectValidationErrs: 0, - validatedPSPHint: "001restrictive", - expectedPSP: "001permissive", - }, - } - for desc, tc := range tests { - t.Run(desc, func(t *testing.T) { - authz := authorizerfactory.NewAlwaysAllowAuthorizer() - allowPrivilegeEscalation := true - pod := goodPod() - pod.Namespace = "ns" - pod.Spec.ServiceAccountName = "sa" - pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = &allowPrivilegeEscalation - - plugin := NewTestAdmission(tc.inPolicies, authz) - attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), "ns", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Update, &metav1.UpdateOptions{}, false, &user.DefaultInfo{Name: "test"}) - - _, pspName, validationErrs, err := plugin.computeSecurityContext(context.Background(), attrs, pod, false, tc.validatedPSPHint) - assert.NoError(t, err) - assert.Len(t, validationErrs, tc.expectValidationErrs) - assert.Equal(t, tc.expectedPSP, pspName) - }) - } -} - -func restrictivePSP() *policy.PodSecurityPolicy { - allowPrivilegeEscalation := false - return &policy.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "restrictive", - Annotations: map[string]string{}, - }, - Spec: policy.PodSecurityPolicySpec{ - AllowPrivilegeEscalation: &allowPrivilegeEscalation, - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: int64(999), Max: int64(999)}, - }, - }, - RunAsGroup: &policy.RunAsGroupStrategyOptions{ - Rule: policy.RunAsGroupStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: int64(999), Max: int64(999)}, - }, - }, - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyMustRunAs, - SELinuxOptions: &v1.SELinuxOptions{ - Level: "s9:z0,z1", - }, - }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: int64(999), Max: int64(999)}, - }, - }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyMustRunAs, - Ranges: []policy.IDRange{ - {Min: int64(999), Max: int64(999)}, - }, - }, - }, - } -} - -func permissivePSP() *policy.PodSecurityPolicy { - allowPrivilegeEscalation := true - return &policy.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "privileged", - Annotations: map[string]string{}, - }, - Spec: policy.PodSecurityPolicySpec{ - AllowPrivilegeEscalation: &allowPrivilegeEscalation, - HostIPC: true, - HostNetwork: true, - HostPID: true, - HostPorts: []policy.HostPortRange{{Min: 0, Max: 65536}}, - Volumes: []policy.FSType{policy.All}, - AllowedCapabilities: []v1.Capability{policy.AllowAllCapabilities}, - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyRunAsAny, - }, - RunAsGroup: &policy.RunAsGroupStrategyOptions{ - Rule: policy.RunAsGroupStrategyRunAsAny, - }, - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyRunAsAny, - }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyRunAsAny, - }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyRunAsAny, - }, - }, - } -} - -// goodPod is empty and should not be used directly for testing since we're providing -// two different PSPs. Since no values are specified it would be allowed to match any -// psp when defaults are filled in. -func goodPod() *kapi.Pod { - return &kapi.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod", - Namespace: "namespace", - Annotations: map[string]string{}, - }, - Spec: kapi.PodSpec{ - ServiceAccountName: "default", - SecurityContext: &kapi.PodSecurityContext{}, - Containers: []kapi.Container{ - { - Name: defaultContainerName, - SecurityContext: &kapi.SecurityContext{}, - }, - }, - }, - } -} diff --git a/test/e2e/auth/pod_security_policy.go b/test/e2e/auth/pod_security_policy.go deleted file mode 100644 index 651aeb4995d..00000000000 --- a/test/e2e/auth/pod_security_policy.go +++ /dev/null @@ -1,367 +0,0 @@ -/* -Copyright 2017 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 auth - -import ( - "context" - "fmt" - - v1 "k8s.io/api/core/v1" - policyv1beta1 "k8s.io/api/policy/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/authentication/serviceaccount" - clientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" - psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" - "k8s.io/kubernetes/test/e2e/framework" - e2eauth "k8s.io/kubernetes/test/e2e/framework/auth" - e2epod "k8s.io/kubernetes/test/e2e/framework/pod" - e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" - imageutils "k8s.io/kubernetes/test/utils/image" - admissionapi "k8s.io/pod-security-admission/api" - utilpointer "k8s.io/utils/pointer" - - "github.com/onsi/ginkgo" -) - -const nobodyUser = int64(65534) - -var _ = SIGDescribe("PodSecurityPolicy [Feature:PodSecurityPolicy]", func() { - f := framework.NewDefaultFramework("podsecuritypolicy") - f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged - f.SkipPrivilegedPSPBinding = true - - // Client that will impersonate the default service account, in order to run - // with reduced privileges. - var c clientset.Interface - var ns string // Test namespace, for convenience - ginkgo.BeforeEach(func() { - if !framework.IsPodSecurityPolicyEnabled(f.ClientSet) { - framework.Failf("PodSecurityPolicy not enabled") - return - } - if !e2eauth.IsRBACEnabled(f.ClientSet.RbacV1()) { - e2eskipper.Skipf("RBAC not enabled") - } - ns = f.Namespace.Name - - ginkgo.By("Creating a kubernetes client that impersonates the default service account") - config, err := framework.LoadConfig() - framework.ExpectNoError(err) - config.Impersonate = restclient.ImpersonationConfig{ - UserName: serviceaccount.MakeUsername(ns, "default"), - Groups: serviceaccount.MakeGroupNames(ns), - } - c, err = clientset.NewForConfig(config) - framework.ExpectNoError(err) - - ginkgo.By("Binding the edit role to the default SA") - err = e2eauth.BindClusterRole(f.ClientSet.RbacV1(), "edit", ns, - rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Namespace: ns, Name: "default"}) - framework.ExpectNoError(err) - }) - - ginkgo.It("should forbid pod creation when no PSP is available", func() { - ginkgo.By("Running a restricted pod") - _, err := c.CoreV1().Pods(ns).Create(context.TODO(), restrictedPod("restricted"), metav1.CreateOptions{}) - expectForbidden(err) - }) - - ginkgo.It("should enforce the restricted policy.PodSecurityPolicy", func() { - ginkgo.By("Creating & Binding a restricted policy for the test service account") - _, cleanup := createAndBindPSP(f, restrictedPSP("restrictive")) - defer cleanup() - - ginkgo.By("Running a restricted pod") - pod, err := c.CoreV1().Pods(ns).Create(context.TODO(), restrictedPod("allowed"), metav1.CreateOptions{}) - framework.ExpectNoError(err) - framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(c, pod.Name, pod.Namespace)) - - testPrivilegedPods(func(pod *v1.Pod) { - _, err := c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{}) - expectForbidden(err) - }) - }) - - ginkgo.It("should allow pods under the privileged policy.PodSecurityPolicy", func() { - ginkgo.By("Creating & Binding a privileged policy for the test service account") - // Ensure that the permissive policy is used even in the presence of the restricted policy. - _, cleanup := createAndBindPSP(f, restrictedPSP("restrictive")) - defer cleanup() - expectedPSP, cleanup := createAndBindPSP(f, privilegedPSP("permissive")) - defer cleanup() - - testPrivilegedPods(func(pod *v1.Pod) { - p, err := c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{}) - framework.ExpectNoError(err) - framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(c, p.Name, p.Namespace)) - - // Verify expected PSP was used. - p, err = c.CoreV1().Pods(ns).Get(context.TODO(), p.Name, metav1.GetOptions{}) - framework.ExpectNoError(err) - validated, found := p.Annotations[psputil.ValidatedPSPAnnotation] - framework.ExpectEqual(found, true, "PSP annotation not found") - framework.ExpectEqual(validated, expectedPSP.Name, "Unexpected validated PSP") - }) - }) -}) - -func expectForbidden(err error) { - framework.ExpectError(err, "should be forbidden") - framework.ExpectEqual(apierrors.IsForbidden(err), true, "should be forbidden error") -} - -func testPrivilegedPods(tester func(pod *v1.Pod)) { - ginkgo.By("Running a privileged pod", func() { - privileged := restrictedPod("privileged") - privileged.Spec.Containers[0].SecurityContext.Privileged = boolPtr(true) - privileged.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = nil - tester(privileged) - }) - - ginkgo.By("Running a HostPath pod", func() { - hostpath := restrictedPod("hostpath") - hostpath.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{{ - Name: "hp", - MountPath: "/hp", - }} - hostpath.Spec.Volumes = []v1.Volume{{ - Name: "hp", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{Path: "/tmp"}, - }, - }} - tester(hostpath) - }) - - ginkgo.By("Running a HostNetwork pod", func() { - hostnet := restrictedPod("hostnet") - hostnet.Spec.HostNetwork = true - tester(hostnet) - }) - - ginkgo.By("Running a HostPID pod", func() { - hostpid := restrictedPod("hostpid") - hostpid.Spec.HostPID = true - tester(hostpid) - }) - - ginkgo.By("Running a HostIPC pod", func() { - hostipc := restrictedPod("hostipc") - hostipc.Spec.HostIPC = true - tester(hostipc) - }) - - ginkgo.By("Running an unconfined Seccomp pod", func() { - unconfined := restrictedPod("seccomp") - unconfined.Annotations[v1.SeccompPodAnnotationKey] = "unconfined" - tester(unconfined) - }) - - ginkgo.By("Running a SYS_ADMIN pod", func() { - sysadmin := restrictedPod("sysadmin") - sysadmin.Spec.Containers[0].SecurityContext.Capabilities = &v1.Capabilities{ - Add: []v1.Capability{"SYS_ADMIN"}, - } - sysadmin.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = nil - tester(sysadmin) - }) - - ginkgo.By("Running a RunAsGroup pod", func() { - sysadmin := restrictedPod("runasgroup") - gid := int64(0) - sysadmin.Spec.Containers[0].SecurityContext.RunAsGroup = &gid - tester(sysadmin) - }) - - ginkgo.By("Running a RunAsUser pod", func() { - sysadmin := restrictedPod("runasuser") - uid := int64(0) - sysadmin.Spec.Containers[0].SecurityContext.RunAsUser = &uid - tester(sysadmin) - }) -} - -// createAndBindPSP creates a PSP in the policy API group. -func createAndBindPSP(f *framework.Framework, pspTemplate *policyv1beta1.PodSecurityPolicy) (psp *policyv1beta1.PodSecurityPolicy, cleanup func()) { - // Create the PodSecurityPolicy object. - psp = pspTemplate.DeepCopy() - // Add the namespace to the name to ensure uniqueness and tie it to the namespace. - ns := f.Namespace.Name - name := fmt.Sprintf("%s-%s", ns, psp.Name) - psp.Name = name - psp, err := f.ClientSet.PolicyV1beta1().PodSecurityPolicies().Create(context.TODO(), psp, metav1.CreateOptions{}) - framework.ExpectNoError(err, "Failed to create PSP") - - // Create the Role to bind it to the namespace. - _, err = f.ClientSet.RbacV1().Roles(ns).Create(context.TODO(), &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Rules: []rbacv1.PolicyRule{{ - APIGroups: []string{"policy"}, - Resources: []string{"podsecuritypolicies"}, - ResourceNames: []string{name}, - Verbs: []string{"use"}, - }}, - }, metav1.CreateOptions{}) - framework.ExpectNoError(err, "Failed to create PSP role") - - // Bind the role to the namespace. - err = e2eauth.BindRoleInNamespace(f.ClientSet.RbacV1(), name, ns, rbacv1.Subject{ - Kind: rbacv1.ServiceAccountKind, - Namespace: ns, - Name: "default", - }) - framework.ExpectNoError(err) - - framework.ExpectNoError(e2eauth.WaitForNamedAuthorizationUpdate(f.ClientSet.AuthorizationV1(), - serviceaccount.MakeUsername(ns, "default"), ns, "use", name, - schema.GroupResource{Group: "policy", Resource: "podsecuritypolicies"}, true)) - - return psp, func() { - // Cleanup non-namespaced PSP object. - f.ClientSet.PolicyV1beta1().PodSecurityPolicies().Delete(context.TODO(), name, metav1.DeleteOptions{}) - } -} - -func restrictedPod(name string) *v1.Pod { - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{ - v1.SeccompPodAnnotationKey: v1.SeccompProfileRuntimeDefault, - v1.AppArmorBetaContainerAnnotationKeyPrefix + "pause": v1.AppArmorBetaProfileRuntimeDefault, - }, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{{ - Name: "pause", - Image: imageutils.GetPauseImageName(), - SecurityContext: &v1.SecurityContext{ - AllowPrivilegeEscalation: boolPtr(false), - RunAsUser: utilpointer.Int64Ptr(nobodyUser), - RunAsGroup: utilpointer.Int64Ptr(nobodyUser), - }, - }}, - }, - } -} - -// privilegedPSPInPolicy creates a PodSecurityPolicy (in the "policy" API Group) that allows everything. -func privilegedPSP(name string) *policyv1beta1.PodSecurityPolicy { - return &policyv1beta1.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny}, - }, - Spec: policyv1beta1.PodSecurityPolicySpec{ - Privileged: true, - AllowPrivilegeEscalation: utilpointer.BoolPtr(true), - AllowedCapabilities: []v1.Capability{"*"}, - Volumes: []policyv1beta1.FSType{policyv1beta1.All}, - HostNetwork: true, - HostPorts: []policyv1beta1.HostPortRange{{Min: 0, Max: 65535}}, - HostIPC: true, - HostPID: true, - RunAsUser: policyv1beta1.RunAsUserStrategyOptions{ - Rule: policyv1beta1.RunAsUserStrategyRunAsAny, - }, - RunAsGroup: &policyv1beta1.RunAsGroupStrategyOptions{ - Rule: policyv1beta1.RunAsGroupStrategyRunAsAny, - }, - SELinux: policyv1beta1.SELinuxStrategyOptions{ - Rule: policyv1beta1.SELinuxStrategyRunAsAny, - }, - SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{ - Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny, - }, - FSGroup: policyv1beta1.FSGroupStrategyOptions{ - Rule: policyv1beta1.FSGroupStrategyRunAsAny, - }, - ReadOnlyRootFilesystem: false, - }, - } -} - -// restrictedPSPInPolicy creates a PodSecurityPolicy (in the "policy" API Group) that is most strict. -func restrictedPSP(name string) *policyv1beta1.PodSecurityPolicy { - return &policyv1beta1.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: v1.SeccompProfileRuntimeDefault, - seccomp.DefaultProfileAnnotationKey: v1.SeccompProfileRuntimeDefault, - v1.AppArmorBetaAllowedProfilesAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - v1.AppArmorBetaDefaultProfileAnnotationKey: v1.AppArmorBetaProfileRuntimeDefault, - }, - }, - Spec: policyv1beta1.PodSecurityPolicySpec{ - Privileged: false, - AllowPrivilegeEscalation: utilpointer.BoolPtr(false), - RequiredDropCapabilities: []v1.Capability{ - "AUDIT_WRITE", - "CHOWN", - "DAC_OVERRIDE", - "FOWNER", - "FSETID", - "KILL", - "MKNOD", - "NET_RAW", - "SETGID", - "SETUID", - "SYS_CHROOT", - }, - Volumes: []policyv1beta1.FSType{ - policyv1beta1.ConfigMap, - policyv1beta1.EmptyDir, - policyv1beta1.PersistentVolumeClaim, - "projected", - policyv1beta1.Secret, - }, - HostNetwork: false, - HostIPC: false, - HostPID: false, - RunAsUser: policyv1beta1.RunAsUserStrategyOptions{ - Rule: policyv1beta1.RunAsUserStrategyMustRunAsNonRoot, - }, - RunAsGroup: &policyv1beta1.RunAsGroupStrategyOptions{ - Rule: policyv1beta1.RunAsGroupStrategyMustRunAs, - Ranges: []policyv1beta1.IDRange{ - {Min: nobodyUser, Max: nobodyUser}}, - }, - SELinux: policyv1beta1.SELinuxStrategyOptions{ - Rule: policyv1beta1.SELinuxStrategyRunAsAny, - }, - SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{ - Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny, - }, - FSGroup: policyv1beta1.FSGroupStrategyOptions{ - Rule: policyv1beta1.FSGroupStrategyRunAsAny, - }, - ReadOnlyRootFilesystem: false, - }, - } -} - -func boolPtr(b bool) *bool { - return &b -} diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index 5743e1ce55b..bfe06550db5 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -83,7 +83,6 @@ type Framework struct { Namespace *v1.Namespace // Every test has at least one namespace unless creation is skipped namespacesToDelete []*v1.Namespace // Some tests have more than one. NamespaceDeletionTimeout time.Duration - SkipPrivilegedPSPBinding bool // Whether to skip creating a binding to the privileged PSP in the test namespace NamespacePodSecurityEnforceLevel admissionapi.Level // The pod security enforcement level for namespaces to be applied. gatherer *ContainerResourceGatherer @@ -545,10 +544,6 @@ func (f *Framework) CreateNamespace(baseName string, labels map[string]string) ( // fail to create serviceAccount in it. f.AddNamespacesToDelete(ns) - if err == nil && !f.SkipPrivilegedPSPBinding { - CreatePrivilegedPSPBinding(f.ClientSet, ns.Name) - } - return ns, err } diff --git a/test/e2e/framework/psp.go b/test/e2e/framework/psp.go deleted file mode 100644 index 6ebd6c24fdf..00000000000 --- a/test/e2e/framework/psp.go +++ /dev/null @@ -1,192 +0,0 @@ -/* -Copyright 2017 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 framework - -import ( - "context" - "fmt" - "strings" - "sync" - - v1 "k8s.io/api/core/v1" - policyv1beta1 "k8s.io/api/policy/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/authentication/serviceaccount" - clientset "k8s.io/client-go/kubernetes" - imageutils "k8s.io/kubernetes/test/utils/image" - - "github.com/onsi/ginkgo" - - // TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245) - e2eauth "k8s.io/kubernetes/test/e2e/framework/auth" -) - -const ( - podSecurityPolicyPrivileged = "e2e-test-privileged-psp" - - // allowAny is the wildcard used to allow any profile. - allowAny = "*" - - // allowedProfilesAnnotationKey specifies the allowed seccomp profiles. - allowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames" -) - -var ( - isPSPEnabledOnce sync.Once - isPSPEnabled bool -) - -// privilegedPSP creates a PodSecurityPolicy that allows everything. -func privilegedPSP(name string) *policyv1beta1.PodSecurityPolicy { - allowPrivilegeEscalation := true - return &policyv1beta1.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{allowedProfilesAnnotationKey: allowAny}, - }, - Spec: policyv1beta1.PodSecurityPolicySpec{ - Privileged: true, - AllowPrivilegeEscalation: &allowPrivilegeEscalation, - AllowedCapabilities: []v1.Capability{"*"}, - Volumes: []policyv1beta1.FSType{policyv1beta1.All}, - HostNetwork: true, - HostPorts: []policyv1beta1.HostPortRange{{Min: 0, Max: 65535}}, - HostIPC: true, - HostPID: true, - RunAsUser: policyv1beta1.RunAsUserStrategyOptions{ - Rule: policyv1beta1.RunAsUserStrategyRunAsAny, - }, - SELinux: policyv1beta1.SELinuxStrategyOptions{ - Rule: policyv1beta1.SELinuxStrategyRunAsAny, - }, - SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{ - Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny, - }, - FSGroup: policyv1beta1.FSGroupStrategyOptions{ - Rule: policyv1beta1.FSGroupStrategyRunAsAny, - }, - ReadOnlyRootFilesystem: false, - AllowedUnsafeSysctls: []string{"*"}, - }, - } -} - -// IsPodSecurityPolicyEnabled returns true if PodSecurityPolicy is enabled. Otherwise false. -func IsPodSecurityPolicyEnabled(kubeClient clientset.Interface) bool { - isPSPEnabledOnce.Do(func() { - psps, err := kubeClient.PolicyV1beta1().PodSecurityPolicies().List(context.TODO(), metav1.ListOptions{}) - if err != nil { - Logf("Error listing PodSecurityPolicies; assuming PodSecurityPolicy is disabled: %v", err) - return - } - if psps == nil || len(psps.Items) == 0 { - Logf("No PodSecurityPolicies found; assuming PodSecurityPolicy is disabled.") - return - } - Logf("Found PodSecurityPolicies; testing pod creation to see if PodSecurityPolicy is enabled") - testPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{GenerateName: "psp-test-pod-"}, - Spec: v1.PodSpec{Containers: []v1.Container{{Name: "test", Image: imageutils.GetPauseImageName()}}}, - } - dryRunPod, err := kubeClient.CoreV1().Pods("kube-system").Create(context.TODO(), testPod, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}) - if err != nil { - if strings.Contains(err.Error(), "PodSecurityPolicy") { - Logf("PodSecurityPolicy error creating dryrun pod; assuming PodSecurityPolicy is enabled: %v", err) - isPSPEnabled = true - } else { - Logf("Error creating dryrun pod; assuming PodSecurityPolicy is disabled: %v", err) - } - return - } - pspAnnotation, pspAnnotationExists := dryRunPod.Annotations["kubernetes.io/psp"] - if !pspAnnotationExists { - Logf("No PSP annotation exists on dry run pod; assuming PodSecurityPolicy is disabled") - return - } - Logf("PSP annotation exists on dry run pod: %q; assuming PodSecurityPolicy is enabled", pspAnnotation) - isPSPEnabled = true - }) - return isPSPEnabled -} - -var ( - privilegedPSPOnce sync.Once -) - -// CreatePrivilegedPSPBinding creates the privileged PSP & role -func CreatePrivilegedPSPBinding(kubeClient clientset.Interface, namespace string) { - if !IsPodSecurityPolicyEnabled(kubeClient) { - return - } - // Create the privileged PSP & role - privilegedPSPOnce.Do(func() { - _, err := kubeClient.PolicyV1beta1().PodSecurityPolicies().Get(context.TODO(), podSecurityPolicyPrivileged, metav1.GetOptions{}) - if !apierrors.IsNotFound(err) { - // Privileged PSP was already created. - ExpectNoError(err, "Failed to get PodSecurityPolicy %s", podSecurityPolicyPrivileged) - return - } - - psp := privilegedPSP(podSecurityPolicyPrivileged) - _, err = kubeClient.PolicyV1beta1().PodSecurityPolicies().Create(context.TODO(), psp, metav1.CreateOptions{}) - if !apierrors.IsAlreadyExists(err) { - ExpectNoError(err, "Failed to create PSP %s", podSecurityPolicyPrivileged) - } - - if e2eauth.IsRBACEnabled(kubeClient.RbacV1()) { - // Create the Role to bind it to the namespace. - _, err = kubeClient.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{Name: podSecurityPolicyPrivileged}, - Rules: []rbacv1.PolicyRule{{ - APIGroups: []string{"extensions"}, - Resources: []string{"podsecuritypolicies"}, - ResourceNames: []string{podSecurityPolicyPrivileged}, - Verbs: []string{"use"}, - }}, - }, metav1.CreateOptions{}) - if !apierrors.IsAlreadyExists(err) { - ExpectNoError(err, "Failed to create PSP role") - } - } - }) - - if e2eauth.IsRBACEnabled(kubeClient.RbacV1()) { - ginkgo.By(fmt.Sprintf("Binding the %s PodSecurityPolicy to the default service account in %s", - podSecurityPolicyPrivileged, namespace)) - err := e2eauth.BindClusterRoleInNamespace(kubeClient.RbacV1(), - podSecurityPolicyPrivileged, - namespace, - rbacv1.Subject{ - Kind: rbacv1.ServiceAccountKind, - Namespace: namespace, - Name: "default", - }, - rbacv1.Subject{ - Kind: rbacv1.GroupKind, - APIGroup: rbacv1.GroupName, - Name: "system:serviceaccounts:" + namespace, - }, - ) - ExpectNoError(err) - ExpectNoError(e2eauth.WaitForNamedAuthorizationUpdate(kubeClient.AuthorizationV1(), - serviceaccount.MakeUsername(namespace, "default"), namespace, "use", podSecurityPolicyPrivileged, - schema.GroupResource{Group: "extensions", Resource: "podsecuritypolicies"}, true)) - } -} diff --git a/test/e2e/storage/utils/utils.go b/test/e2e/storage/utils/utils.go index 50b1f6eca0f..f56a64316bd 100644 --- a/test/e2e/storage/utils/utils.go +++ b/test/e2e/storage/utils/utils.go @@ -32,14 +32,12 @@ import ( "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/dynamic" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/test/e2e/framework" @@ -65,11 +63,6 @@ const ( maxValidSize string = "10Ei" ) -const ( - // ClusterRole name for e2e test Priveledged Pod Security Policy User - podSecurityPolicyPrivilegedClusterRoleName = "e2e-test-privileged-psp" -) - // VerifyFSGroupInPod verifies that the passed in filePath contains the expectedFSGroup func VerifyFSGroupInPod(f *framework.Framework, filePath, expectedFSGroup string, pod *v1.Pod) { cmd := fmt.Sprintf("ls -l %s", filePath) @@ -417,54 +410,6 @@ func StartExternalProvisioner(c clientset.Interface, ns string, externalPluginNa return pod } -// PrivilegedTestPSPClusterRoleBinding test Pod Security Policy Role bindings -func PrivilegedTestPSPClusterRoleBinding(client clientset.Interface, - namespace string, - teardown bool, - saNames []string) { - bindingString := "Binding" - if teardown { - bindingString = "Unbinding" - } - roleBindingClient := client.RbacV1().RoleBindings(namespace) - for _, saName := range saNames { - ginkgo.By(fmt.Sprintf("%v priviledged Pod Security Policy to the service account %s", bindingString, saName)) - binding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "psp-" + saName, - Namespace: namespace, - }, - Subjects: []rbacv1.Subject{ - { - Kind: rbacv1.ServiceAccountKind, - Name: saName, - Namespace: namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "ClusterRole", - Name: podSecurityPolicyPrivilegedClusterRoleName, - APIGroup: "rbac.authorization.k8s.io", - }, - } - - roleBindingClient.Delete(context.TODO(), binding.GetName(), metav1.DeleteOptions{}) - err := wait.Poll(2*time.Second, 2*time.Minute, func() (bool, error) { - _, err := roleBindingClient.Get(context.TODO(), binding.GetName(), metav1.GetOptions{}) - return apierrors.IsNotFound(err), nil - }) - framework.ExpectNoError(err, "Timed out waiting for RBAC binding %s deletion: %v", binding.GetName(), err) - - if teardown { - continue - } - - _, err = roleBindingClient.Create(context.TODO(), binding, metav1.CreateOptions{}) - framework.ExpectNoError(err, "Failed to create %s role binding: %v", binding.GetName(), err) - - } -} - func isSudoPresent(nodeIP string, provider string) bool { framework.Logf("Checking if sudo command is present") sshResult, err := e2essh.SSH("sudo --version", nodeIP, provider)