From b783c6e25bf67f50e69e95ada007bfd8b77e0c95 Mon Sep 17 00:00:00 2001 From: Slava Semushin Date: Fri, 23 Feb 2018 18:34:04 +0100 Subject: [PATCH 1/2] E2E: add tests for PSP from the "policy" API Group. --- test/e2e/auth/pod_security_policy.go | 295 +++++++++++++++++++++------ 1 file changed, 233 insertions(+), 62 deletions(-) diff --git a/test/e2e/auth/pod_security_policy.go b/test/e2e/auth/pod_security_policy.go index de60c0a1a82..a2bbbcb88b9 100644 --- a/test/e2e/auth/pod_security_policy.go +++ b/test/e2e/auth/pod_security_policy.go @@ -21,6 +21,7 @@ import ( "k8s.io/api/core/v1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + policy "k8s.io/api/policy/v1beta1" rbacv1beta1 "k8s.io/api/rbac/v1beta1" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" "k8s.io/kubernetes/test/e2e/common" "k8s.io/kubernetes/test/e2e/framework" @@ -38,64 +40,6 @@ import ( . "github.com/onsi/gomega" ) -var ( - restrictivePSPTemplate = &extensionsv1beta1.PodSecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "restrictive", - Annotations: map[string]string{ - seccomp.AllowedProfilesAnnotationKey: "docker/default", - seccomp.DefaultProfileAnnotationKey: "docker/default", - apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault, - apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault, - }, - Labels: map[string]string{ - "kubernetes.io/cluster-service": "true", - "addonmanager.kubernetes.io/mode": "Reconcile", - }, - }, - Spec: extensionsv1beta1.PodSecurityPolicySpec{ - Privileged: false, - AllowPrivilegeEscalation: boolPtr(false), - RequiredDropCapabilities: []v1.Capability{ - "AUDIT_WRITE", - "CHOWN", - "DAC_OVERRIDE", - "FOWNER", - "FSETID", - "KILL", - "MKNOD", - "NET_RAW", - "SETGID", - "SETUID", - "SYS_CHROOT", - }, - Volumes: []extensionsv1beta1.FSType{ - extensionsv1beta1.ConfigMap, - extensionsv1beta1.EmptyDir, - extensionsv1beta1.PersistentVolumeClaim, - "projected", - extensionsv1beta1.Secret, - }, - HostNetwork: false, - HostIPC: false, - HostPID: false, - RunAsUser: extensionsv1beta1.RunAsUserStrategyOptions{ - Rule: extensionsv1beta1.RunAsUserStrategyMustRunAsNonRoot, - }, - SELinux: extensionsv1beta1.SELinuxStrategyOptions{ - Rule: extensionsv1beta1.SELinuxStrategyRunAsAny, - }, - SupplementalGroups: extensionsv1beta1.SupplementalGroupsStrategyOptions{ - Rule: extensionsv1beta1.SupplementalGroupsStrategyRunAsAny, - }, - FSGroup: extensionsv1beta1.FSGroupStrategyOptions{ - Rule: extensionsv1beta1.FSGroupStrategyRunAsAny, - }, - ReadOnlyRootFilesystem: false, - }, - } -) - var _ = SIGDescribe("PodSecurityPolicy", func() { f := framework.NewDefaultFramework("podsecuritypolicy") f.SkipPrivilegedPSPBinding = true @@ -134,9 +78,11 @@ var _ = SIGDescribe("PodSecurityPolicy", func() { expectForbidden(err) }) - It("should enforce the restricted PodSecurityPolicy", func() { + // TODO: merge tests for extensions/policy API groups when PSP will be completely moved out of the extensions + + It("should enforce the restricted extensions.PodSecurityPolicy", func() { By("Creating & Binding a restricted policy for the test service account") - _, cleanup := createAndBindPSP(f, restrictivePSPTemplate) + _, cleanup := createAndBindPSP(f, restrictedPSP("restrictive")) defer cleanup() By("Running a restricted pod") @@ -150,10 +96,26 @@ var _ = SIGDescribe("PodSecurityPolicy", func() { }) }) - It("should allow pods under the privileged PodSecurityPolicy", func() { + It("should enforce the restricted policy.PodSecurityPolicy", func() { + By("Creating & Binding a restricted policy for the test service account") + _, cleanup := createAndBindPSPInPolicy(f, restrictedPSPInPolicy("restrictive")) + defer cleanup() + + By("Running a restricted pod") + pod, err := c.CoreV1().Pods(ns).Create(restrictedPod(f, "allowed")) + framework.ExpectNoError(err) + framework.ExpectNoError(framework.WaitForPodNameRunningInNamespace(c, pod.Name, pod.Namespace)) + + testPrivilegedPods(f, func(pod *v1.Pod) { + _, err := c.CoreV1().Pods(ns).Create(pod) + expectForbidden(err) + }) + }) + + It("should allow pods under the privileged extensions.PodSecurityPolicy", func() { 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, restrictivePSPTemplate) + _, cleanup := createAndBindPSP(f, restrictedPSP("restrictive")) defer cleanup() expectedPSP, cleanup := createAndBindPSP(f, framework.PrivilegedPSP("permissive")) defer cleanup() @@ -171,6 +133,28 @@ var _ = SIGDescribe("PodSecurityPolicy", func() { Expect(validated).To(Equal(expectedPSP.Name), "Unexpected validated PSP") }) }) + + It("should allow pods under the privileged policy.PodSecurityPolicy", func() { + 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 := createAndBindPSPInPolicy(f, restrictedPSPInPolicy("restrictive")) + defer cleanup() + expectedPSP, cleanup := createAndBindPSPInPolicy(f, privilegedPSPInPolicy("permissive")) + defer cleanup() + + testPrivilegedPods(f, func(pod *v1.Pod) { + p, err := c.CoreV1().Pods(ns).Create(pod) + framework.ExpectNoError(err) + framework.ExpectNoError(framework.WaitForPodNameRunningInNamespace(c, p.Name, p.Namespace)) + + // Verify expected PSP was used. + p, err = c.CoreV1().Pods(ns).Get(p.Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + validated, found := p.Annotations[psputil.ValidatedPSPAnnotation] + Expect(found).To(BeTrue(), "PSP annotation not found") + Expect(validated).To(Equal(expectedPSP.Name), "Unexpected validated PSP") + }) + }) }) func expectForbidden(err error) { @@ -284,6 +268,48 @@ func createAndBindPSP(f *framework.Framework, pspTemplate *extensionsv1beta1.Pod } } +// createAndBindPSPInPolicy creates a PSP in the policy API group (unlike createAndBindPSP()). +// TODO: merge these functions when PSP will be completely moved out of the extensions +func createAndBindPSPInPolicy(f *framework.Framework, pspTemplate *policy.PodSecurityPolicy) (psp *policy.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(psp) + framework.ExpectNoError(err, "Failed to create PSP") + + // Create the Role to bind it to the namespace. + _, err = f.ClientSet.RbacV1beta1().Roles(ns).Create(&rbacv1beta1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Rules: []rbacv1beta1.PolicyRule{{ + APIGroups: []string{"policy"}, + Resources: []string{"podsecuritypolicies"}, + ResourceNames: []string{name}, + Verbs: []string{"use"}, + }}, + }) + framework.ExpectNoError(err, "Failed to create PSP role") + + // Bind the role to the namespace. + framework.BindRoleInNamespace(f.ClientSet.RbacV1beta1(), name, ns, rbacv1beta1.Subject{ + Kind: rbacv1beta1.ServiceAccountKind, + Namespace: ns, + Name: "default", + }) + framework.ExpectNoError(framework.WaitForNamedAuthorizationUpdate(f.ClientSet.AuthorizationV1beta1(), + 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(name, &metav1.DeleteOptions{}) + } +} + func restrictedPod(f *framework.Framework, name string) *v1.Pod { return &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -306,6 +332,151 @@ func restrictedPod(f *framework.Framework, name string) *v1.Pod { } } +// privilegedPSPInPolicy creates a PodSecurityPolicy (in the "policy" API Group) that allows everything. +// TODO: replace by PrivilegedPSP when PSP will be completely moved out of the extensions +func privilegedPSPInPolicy(name string) *policy.PodSecurityPolicy { + return &policy.PodSecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: map[string]string{seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny}, + }, + Spec: policy.PodSecurityPolicySpec{ + Privileged: true, + AllowPrivilegeEscalation: utilpointer.BoolPtr(true), + AllowedCapabilities: []v1.Capability{"*"}, + Volumes: []policy.FSType{policy.All}, + HostNetwork: true, + HostPorts: []policy.HostPortRange{{Min: 0, Max: 65535}}, + HostIPC: true, + HostPID: true, + RunAsUser: policy.RunAsUserStrategyOptions{ + Rule: policy.RunAsUserStrategyRunAsAny, + }, + SELinux: policy.SELinuxStrategyOptions{ + Rule: policy.SELinuxStrategyRunAsAny, + }, + SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ + Rule: policy.SupplementalGroupsStrategyRunAsAny, + }, + FSGroup: policy.FSGroupStrategyOptions{ + Rule: policy.FSGroupStrategyRunAsAny, + }, + ReadOnlyRootFilesystem: false, + }, + } +} + +// restrictedPSPInPolicy creates a PodSecurityPolicy (in the "policy" API Group) that is most strict. +// TODO: replace by restrictedPSP when PSP will be completely moved out of the extensions +func restrictedPSPInPolicy(name string) *policy.PodSecurityPolicy { + return &policy.PodSecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: map[string]string{ + seccomp.AllowedProfilesAnnotationKey: "docker/default", + seccomp.DefaultProfileAnnotationKey: "docker/default", + apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault, + apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault, + }, + }, + Spec: policy.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: []policy.FSType{ + policy.ConfigMap, + policy.EmptyDir, + policy.PersistentVolumeClaim, + "projected", + policy.Secret, + }, + HostNetwork: false, + HostIPC: false, + HostPID: false, + RunAsUser: policy.RunAsUserStrategyOptions{ + Rule: policy.RunAsUserStrategyMustRunAsNonRoot, + }, + SELinux: policy.SELinuxStrategyOptions{ + Rule: policy.SELinuxStrategyRunAsAny, + }, + SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ + Rule: policy.SupplementalGroupsStrategyRunAsAny, + }, + FSGroup: policy.FSGroupStrategyOptions{ + Rule: policy.FSGroupStrategyRunAsAny, + }, + ReadOnlyRootFilesystem: false, + }, + } +} + +// restrictedPSP creates a PodSecurityPolicy that is most strict. +func restrictedPSP(name string) *extensionsv1beta1.PodSecurityPolicy { + return &extensionsv1beta1.PodSecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: map[string]string{ + seccomp.AllowedProfilesAnnotationKey: "docker/default", + seccomp.DefaultProfileAnnotationKey: "docker/default", + apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault, + apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault, + }, + }, + Spec: extensionsv1beta1.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: []extensionsv1beta1.FSType{ + extensionsv1beta1.ConfigMap, + extensionsv1beta1.EmptyDir, + extensionsv1beta1.PersistentVolumeClaim, + "projected", + extensionsv1beta1.Secret, + }, + HostNetwork: false, + HostIPC: false, + HostPID: false, + RunAsUser: extensionsv1beta1.RunAsUserStrategyOptions{ + Rule: extensionsv1beta1.RunAsUserStrategyMustRunAsNonRoot, + }, + SELinux: extensionsv1beta1.SELinuxStrategyOptions{ + Rule: extensionsv1beta1.SELinuxStrategyRunAsAny, + }, + SupplementalGroups: extensionsv1beta1.SupplementalGroupsStrategyOptions{ + Rule: extensionsv1beta1.SupplementalGroupsStrategyRunAsAny, + }, + FSGroup: extensionsv1beta1.FSGroupStrategyOptions{ + Rule: extensionsv1beta1.FSGroupStrategyRunAsAny, + }, + ReadOnlyRootFilesystem: false, + }, + } +} + func boolPtr(b bool) *bool { return &b } From 4e273a69f7137328ed2b3583bbdfd5d667dd5bad Mon Sep 17 00:00:00 2001 From: Slava Semushin Date: Fri, 23 Feb 2018 20:36:11 +0100 Subject: [PATCH 2/2] Run hack/update-bazel.sh --- test/e2e/auth/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/e2e/auth/BUILD b/test/e2e/auth/BUILD index 708c7d9e68c..f16056db11f 100644 --- a/test/e2e/auth/BUILD +++ b/test/e2e/auth/BUILD @@ -21,6 +21,7 @@ go_library( "//pkg/security/apparmor:go_default_library", "//pkg/security/podsecuritypolicy/seccomp:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library", + "//pkg/util/pointer:go_default_library", "//plugin/pkg/admission/serviceaccount:go_default_library", "//test/e2e/common:go_default_library", "//test/e2e/framework:go_default_library", @@ -32,6 +33,7 @@ go_library( "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",