func TestValidateContainerSecurityContextFailures(t *testing.T) { // fail user strat failUserPSP := defaultPSP() var uid int64 = 999 var badUID int64 = 1 failUserPSP.Spec.RunAsUser = extensions.RunAsUserStrategyOptions{ Rule: extensions.RunAsUserStrategyMustRunAs, Ranges: []extensions.IDRange{{Min: uid, Max: uid}}, } failUserPod := defaultPod() failUserPod.Spec.Containers[0].SecurityContext.RunAsUser = &badUID // fail selinux strat failSELinuxPSP := defaultPSP() failSELinuxPSP.Spec.SELinux = extensions.SELinuxStrategyOptions{ Rule: extensions.SELinuxStrategyMustRunAs, SELinuxOptions: &api.SELinuxOptions{ Level: "foo", }, } failSELinuxPod := defaultPod() failSELinuxPod.Spec.Containers[0].SecurityContext.SELinuxOptions = &api.SELinuxOptions{ Level: "bar", } failNilAppArmorPod := defaultPod() failInvalidAppArmorPod := defaultPod() apparmor.SetProfileName(failInvalidAppArmorPod, defaultContainerName, apparmor.ProfileNamePrefix+"foo") failAppArmorPSP := defaultPSP() failAppArmorPSP.Annotations = map[string]string{ apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault, } failPrivPod := defaultPod() var priv bool = true failPrivPod.Spec.Containers[0].SecurityContext.Privileged = &priv 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 errorCases := map[string]struct { pod *api.Pod psp *extensions.PodSecurityPolicy expectedError string }{ "failUserPSP": { pod: failUserPod, psp: failUserPSP, expectedError: "does not match required range", }, "failSELinuxPSP": { pod: failSELinuxPod, psp: failSELinuxPSP, expectedError: "does not match required level", }, "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", }, "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", }, } for k, v := range errorCases { provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) if err != nil { t.Fatalf("unable to create provider %v", err) } errs := provider.ValidateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0], field.NewPath("")) if len(errs) == 0 { t.Errorf("%s expected validation failure but did not receive errors", k) continue } if !strings.Contains(errs[0].Error(), v.expectedError) { t.Errorf("%s received unexpected error %v", k, errs) } } }
func TestValidateContainerSecurityContextSuccess(t *testing.T) { var notPriv bool = false defaultPod := func() *api.Pod { return &api.Pod{ Spec: api.PodSpec{ SecurityContext: &api.PodSecurityContext{}, Containers: []api.Container{ { Name: defaultContainerName, SecurityContext: &api.SecurityContext{ // expected to be set by defaulting mechanisms Privileged: ¬Priv, // fill in the rest for test cases }, }, }, }, } } // success user strat userPSP := defaultPSP() var uid int64 = 999 userPSP.Spec.RunAsUser = extensions.RunAsUserStrategyOptions{ Rule: extensions.RunAsUserStrategyMustRunAs, Ranges: []extensions.IDRange{{Min: uid, Max: uid}}, } userPod := defaultPod() userPod.Spec.Containers[0].SecurityContext.RunAsUser = &uid // success selinux strat seLinuxPSP := defaultPSP() seLinuxPSP.Spec.SELinux = extensions.SELinuxStrategyOptions{ Rule: extensions.SELinuxStrategyMustRunAs, SELinuxOptions: &api.SELinuxOptions{ Level: "foo", }, } seLinuxPod := defaultPod() seLinuxPod.Spec.Containers[0].SecurityContext.SELinuxOptions = &api.SELinuxOptions{ Level: "foo", } appArmorPSP := defaultPSP() appArmorPSP.Annotations = map[string]string{ apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault, } appArmorPod := defaultPod() apparmor.SetProfileName(appArmorPod, defaultContainerName, apparmor.ProfileRuntimeDefault) privPSP := defaultPSP() privPSP.Spec.Privileged = true privPod := defaultPod() var priv bool = true privPod.Spec.Containers[0].SecurityContext.Privileged = &priv capsPSP := defaultPSP() capsPSP.Spec.AllowedCapabilities = []api.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 = []api.Capability{"foo"} requiredCapsPod := defaultPod() requiredCapsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{ Add: []api.Capability{"foo"}, } hostDirPSP := defaultPSP() hostDirPSP.Spec.Volumes = []extensions.FSType{extensions.HostPath} hostDirPod := defaultPod() hostDirPod.Spec.Volumes = []api.Volume{ { Name: "bad volume", VolumeSource: api.VolumeSource{ HostPath: &api.HostPathVolumeSource{}, }, }, } hostPortPSP := defaultPSP() hostPortPSP.Spec.HostPorts = []extensions.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 errorCases := map[string]struct { pod *api.Pod psp *extensions.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(), }, } for k, v := range errorCases { provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) if err != nil { t.Fatalf("unable to create provider %v", err) } errs := provider.ValidateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0], field.NewPath("")) if len(errs) != 0 { t.Errorf("%s expected validation pass but received errors %v\n%s", k, errs, spew.Sdump(v.pod.ObjectMeta)) continue } } }
func TestAdmitAppArmor(t *testing.T) { createPodWithAppArmor := func(profile string) *kapi.Pod { pod := goodPod() apparmor.SetProfileName(pod, defaultContainerName, profile) return pod } unconstrainedPSP := restrictivePSP() defaultedPSP := restrictivePSP() defaultedPSP.Annotations = map[string]string{ apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault, } appArmorPSP := restrictivePSP() appArmorPSP.Annotations = map[string]string{ apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault, } appArmorDefaultPSP := restrictivePSP() appArmorDefaultPSP.Annotations = map[string]string{ apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault, apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo", } tests := map[string]struct { pod *kapi.Pod psp *extensions.PodSecurityPolicy shouldPass bool expectedProfile string }{ "unconstrained with no profile": { pod: goodPod(), psp: unconstrainedPSP, shouldPass: true, expectedProfile: "", }, "unconstrained with profile": { pod: createPodWithAppArmor(apparmor.ProfileRuntimeDefault), psp: unconstrainedPSP, shouldPass: true, expectedProfile: apparmor.ProfileRuntimeDefault, }, "unconstrained with default profile": { pod: goodPod(), psp: defaultedPSP, shouldPass: true, expectedProfile: apparmor.ProfileRuntimeDefault, }, "AppArmor enforced with no profile": { pod: goodPod(), psp: appArmorPSP, shouldPass: false, }, "AppArmor enforced with default profile": { pod: goodPod(), psp: appArmorDefaultPSP, shouldPass: true, expectedProfile: apparmor.ProfileRuntimeDefault, }, "AppArmor enforced with good profile": { pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "foo"), psp: appArmorDefaultPSP, shouldPass: true, expectedProfile: apparmor.ProfileNamePrefix + "foo", }, "AppArmor enforced with local profile": { pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "bar"), psp: appArmorPSP, shouldPass: false, }, } for k, v := range tests { testPSPAdmit(k, []*extensions.PodSecurityPolicy{v.psp}, v.pod, v.shouldPass, v.psp.Name, t) if v.shouldPass { assert.Equal(t, v.expectedProfile, apparmor.GetProfileName(v.pod, defaultContainerName), k) } } }