123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- /*
- 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/validation/field"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- api "k8s.io/kubernetes/pkg/apis/core"
- "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
- }
- for i := range pod.Spec.InitContainers {
- if err := s.mutateContainer(pod, &pod.Spec.InitContainers[i]); err != nil {
- return err
- }
- }
- for i := range pod.Spec.Containers {
- if err := s.mutateContainer(pod, &pod.Spec.Containers[i]); err != nil {
- return err
- }
- }
- return nil
- }
- // 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 utilfeature.DefaultFeatureGate.Enabled(features.RunAsGroup) {
- 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)...)
- }
- fldPath := field.NewPath("spec", "initContainers")
- for i := range pod.Spec.InitContainers {
- allErrs = append(allErrs, s.validateContainer(pod, &pod.Spec.InitContainers[i], fldPath.Index(i))...)
- }
- fldPath = field.NewPath("spec", "containers")
- for i := range pod.Spec.Containers {
- allErrs = append(allErrs, s.validateContainer(pod, &pod.Spec.Containers[i], fldPath.Index(i))...)
- }
- 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 && !allowedVolumes.Has(string(fsType)) {
- 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
- for i, c := range pod.Spec.InitContainers {
- for j, cv := range c.VolumeMounts {
- if cv.Name == v.Name && !cv.ReadOnly {
- allErrs = append(allErrs, field.Invalid(
- field.NewPath("spec", "initContainers").Index(i).Child("volumeMounts").Index(j).Child("readOnly"),
- cv.ReadOnly, "must be read-only"),
- )
- }
- }
- }
- for i, c := range pod.Spec.Containers {
- for j, cv := range c.VolumeMounts {
- if cv.Name == v.Name && !cv.ReadOnly {
- allErrs = append(allErrs, field.Invalid(
- field.NewPath("spec", "containers").Index(i).Child("volumeMounts").Index(j).Child("readOnly"),
- cv.ReadOnly, "must be read-only"),
- )
- }
- }
- }
- }
- 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())...)
- if utilfeature.DefaultFeatureGate.Enabled(features.RunAsGroup) {
- 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, "")}
- }
|