123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- /*
- 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"
- "k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/apis/extensions"
- psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
- "k8s.io/kubernetes/pkg/util/maps"
- "k8s.io/kubernetes/pkg/util/validation/field"
- )
- // used to pass in the field being validated for reusable group strategies so they
- // can create informative error messages.
- const (
- fsGroupField = "fsGroup"
- supplementalGroupsField = "supplementalGroups"
- )
- // simpleProvider is the default implementation of Provider.
- type simpleProvider struct {
- psp *extensions.PodSecurityPolicy
- strategies *ProviderStrategies
- }
- // ensure we implement the interface correctly.
- var _ Provider = &simpleProvider{}
- // NewSimpleProvider creates a new Provider instance.
- func NewSimpleProvider(psp *extensions.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
- }
- // Create a PodSecurityContext based on the given constraints. If a setting is already set
- // on the PodSecurityContext it will not be changed. Validate should be used after the context
- // is created to ensure it complies with the required restrictions.
- //
- // NOTE: this method works on a copy of the PodSecurityContext. It is up to the caller to
- // apply the PSC if validation passes.
- func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurityContext, map[string]string, error) {
- var sc *api.PodSecurityContext = nil
- if pod.Spec.SecurityContext != nil {
- // work with a copy
- copy := *pod.Spec.SecurityContext
- sc = ©
- } else {
- sc = &api.PodSecurityContext{}
- }
- annotations := maps.CopySS(pod.Annotations)
- if len(sc.SupplementalGroups) == 0 {
- supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod)
- if err != nil {
- return nil, nil, err
- }
- sc.SupplementalGroups = supGroups
- }
- if sc.FSGroup == nil {
- fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod)
- if err != nil {
- return nil, nil, err
- }
- sc.FSGroup = fsGroup
- }
- if sc.SELinuxOptions == nil {
- seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil)
- if err != nil {
- return nil, nil, err
- }
- sc.SELinuxOptions = seLinux
- }
- return sc, annotations, nil
- }
- // Create a SecurityContext based on the given constraints. If a setting is already set on the
- // container's security context then it will not be changed. Validation should be used after
- // the context is created to ensure it complies with the required restrictions.
- //
- // NOTE: this method works on a copy of the SC of the container. It is up to the caller to apply
- // the SC if validation passes.
- func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container *api.Container) (*api.SecurityContext, map[string]string, error) {
- var sc *api.SecurityContext = nil
- if container.SecurityContext != nil {
- // work with a copy of the original
- copy := *container.SecurityContext
- sc = ©
- } else {
- sc = &api.SecurityContext{}
- }
- annotations := maps.CopySS(pod.Annotations)
- if sc.RunAsUser == nil {
- uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container)
- if err != nil {
- return nil, nil, err
- }
- sc.RunAsUser = uid
- }
- if sc.SELinuxOptions == nil {
- seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container)
- if err != nil {
- return nil, nil, err
- }
- sc.SELinuxOptions = seLinux
- }
- annotations, err := s.strategies.AppArmorStrategy.Generate(annotations, container)
- if err != nil {
- return nil, nil, err
- }
- if sc.Privileged == nil {
- priv := false
- sc.Privileged = &priv
- }
- // 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 s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot {
- nonRoot := true
- sc.RunAsNonRoot = &nonRoot
- }
- caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container)
- if err != nil {
- return nil, nil, err
- }
- sc.Capabilities = 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.ReadOnlyRootFilesystem = &readOnlyRootFS
- }
- return sc, annotations, nil
- }
- // Ensure a pod's SecurityContext is in compliance with the given constraints.
- func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if pod.Spec.SecurityContext == nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("securityContext"), pod.Spec.SecurityContext, "No security context is set"))
- return allErrs
- }
- fsGroups := []int64{}
- if pod.Spec.SecurityContext.FSGroup != nil {
- fsGroups = append(fsGroups, *pod.Spec.SecurityContext.FSGroup)
- }
- allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(pod, fsGroups)...)
- allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(pod, pod.Spec.SecurityContext.SupplementalGroups)...)
- // make a dummy container context to reuse the selinux strategies
- container := &api.Container{
- Name: pod.Name,
- SecurityContext: &api.SecurityContext{
- SELinuxOptions: pod.Spec.SecurityContext.SELinuxOptions,
- },
- }
- allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
- if !s.psp.Spec.HostNetwork && pod.Spec.SecurityContext.HostNetwork {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), pod.Spec.SecurityContext.HostNetwork, "Host network is not allowed to be used"))
- }
- if !s.psp.Spec.HostPID && pod.Spec.SecurityContext.HostPID {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), pod.Spec.SecurityContext.HostPID, "Host PID is not allowed to be used"))
- }
- if !s.psp.Spec.HostIPC && pod.Spec.SecurityContext.HostIPC {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), pod.Spec.SecurityContext.HostIPC, "Host IPC is not allowed to be used"))
- }
- allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...)
- return allErrs
- }
- // Ensure a container's SecurityContext is in compliance with the given constraints
- func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, container *api.Container, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if container.SecurityContext == nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("securityContext"), container.SecurityContext, "No security context is set"))
- return allErrs
- }
- sc := container.SecurityContext
- allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...)
- allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
- allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...)
- if !s.psp.Spec.Privileged && *sc.Privileged {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed"))
- }
- allErrs = append(allErrs, s.strategies.CapabilitiesStrategy.Validate(pod, container)...)
- if len(pod.Spec.Volumes) > 0 && !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(fldPath.Child("volumes").Index(i), string(fsType), err.Error()))
- continue
- }
- if !allowedVolumes.Has(string(fsType)) {
- allErrs = append(allErrs, field.Invalid(
- fldPath.Child("volumes").Index(i), string(fsType),
- fmt.Sprintf("%s volumes are not allowed to be used", string(fsType))))
- }
- }
- }
- if !s.psp.Spec.HostNetwork && pod.Spec.SecurityContext.HostNetwork {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), pod.Spec.SecurityContext.HostNetwork, "Host network is not allowed to be used"))
- }
- containersPath := fldPath.Child("containers")
- for idx, c := range pod.Spec.Containers {
- idxPath := containersPath.Index(idx)
- allErrs = append(allErrs, s.hasInvalidHostPort(&c, idxPath)...)
- }
- containersPath = fldPath.Child("initContainers")
- for idx, c := range pod.Spec.InitContainers {
- idxPath := containersPath.Index(idx)
- allErrs = append(allErrs, s.hasInvalidHostPort(&c, idxPath)...)
- }
- if !s.psp.Spec.HostPID && pod.Spec.SecurityContext.HostPID {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), pod.Spec.SecurityContext.HostPID, "Host PID is not allowed to be used"))
- }
- if !s.psp.Spec.HostIPC && pod.Spec.SecurityContext.HostIPC {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), pod.Spec.SecurityContext.HostIPC, "Host IPC is not allowed to be used"))
- }
- if s.psp.Spec.ReadOnlyRootFilesystem {
- if sc.ReadOnlyRootFilesystem == nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), sc.ReadOnlyRootFilesystem, "ReadOnlyRootFilesystem may not be nil and must be set to true"))
- } else if !*sc.ReadOnlyRootFilesystem {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), *sc.ReadOnlyRootFilesystem, "ReadOnlyRootFilesystem must be set to true"))
- }
- }
- return allErrs
- }
- // hasHostPort checks the port definitions on the container for HostPort > 0.
- 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(int(cp.HostPort)) {
- detail := fmt.Sprintf("Host port %d is not allowed to be used. Allowed ports: %v", cp.HostPort, 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 int) 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
- }
|