2016-05-05 19:43:54 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-05-05 19:43:54 +00:00
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"
2017-04-20 10:57:07 +00:00
"k8s.io/apimachinery/pkg/types"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/validation/field"
2016-05-05 19:43:54 +00:00
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
2016-08-17 17:14:23 +00:00
"k8s.io/kubernetes/pkg/util/maps"
2016-05-05 19:43:54 +00:00
)
// 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.
2016-08-17 17:14:23 +00:00
func ( s * simpleProvider ) CreatePodSecurityContext ( pod * api . Pod ) ( * api . PodSecurityContext , map [ string ] string , error ) {
2016-05-05 19:43:54 +00:00
var sc * api . PodSecurityContext = nil
if pod . Spec . SecurityContext != nil {
// work with a copy
copy := * pod . Spec . SecurityContext
sc = & copy
} else {
sc = & api . PodSecurityContext { }
}
2016-08-17 17:14:23 +00:00
annotations := maps . CopySS ( pod . Annotations )
2016-05-05 19:43:54 +00:00
if len ( sc . SupplementalGroups ) == 0 {
supGroups , err := s . strategies . SupplementalGroupStrategy . Generate ( pod )
if err != nil {
2016-08-17 17:14:23 +00:00
return nil , nil , err
2016-05-05 19:43:54 +00:00
}
sc . SupplementalGroups = supGroups
}
if sc . FSGroup == nil {
fsGroup , err := s . strategies . FSGroupStrategy . GenerateSingle ( pod )
if err != nil {
2016-08-17 17:14:23 +00:00
return nil , nil , err
2016-05-05 19:43:54 +00:00
}
sc . FSGroup = fsGroup
}
if sc . SELinuxOptions == nil {
seLinux , err := s . strategies . SELinuxStrategy . Generate ( pod , nil )
if err != nil {
2016-08-17 17:14:23 +00:00
return nil , nil , err
2016-05-05 19:43:54 +00:00
}
sc . SELinuxOptions = seLinux
}
2016-08-23 16:52:27 +00:00
// 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 ( annotations , pod )
if err != nil {
return nil , nil , err
}
if seccompProfile != "" {
if annotations == nil {
annotations = map [ string ] string { }
}
annotations [ api . SeccompPodAnnotationKey ] = seccompProfile
}
2016-08-17 17:14:23 +00:00
return sc , annotations , nil
2016-05-05 19:43:54 +00:00
}
// 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.
2016-08-17 17:14:23 +00:00
func ( s * simpleProvider ) CreateContainerSecurityContext ( pod * api . Pod , container * api . Container ) ( * api . SecurityContext , map [ string ] string , error ) {
2016-05-05 19:43:54 +00:00
var sc * api . SecurityContext = nil
if container . SecurityContext != nil {
// work with a copy of the original
copy := * container . SecurityContext
sc = & copy
} else {
sc = & api . SecurityContext { }
}
2016-08-17 17:14:23 +00:00
annotations := maps . CopySS ( pod . Annotations )
2016-05-05 19:43:54 +00:00
if sc . RunAsUser == nil {
uid , err := s . strategies . RunAsUserStrategy . Generate ( pod , container )
if err != nil {
2016-08-17 17:14:23 +00:00
return nil , nil , err
2016-05-05 19:43:54 +00:00
}
sc . RunAsUser = uid
}
if sc . SELinuxOptions == nil {
seLinux , err := s . strategies . SELinuxStrategy . Generate ( pod , container )
if err != nil {
2016-08-17 17:14:23 +00:00
return nil , nil , err
2016-05-05 19:43:54 +00:00
}
sc . SELinuxOptions = seLinux
}
2016-08-18 00:24:47 +00:00
annotations , err := s . strategies . AppArmorStrategy . Generate ( annotations , container )
if err != nil {
return nil , nil , err
}
2016-05-05 19:43:54 +00:00
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 {
2016-08-17 17:14:23 +00:00
return nil , nil , err
2016-05-05 19:43:54 +00:00
}
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
}
2016-08-17 17:14:23 +00:00
return sc , annotations , nil
2016-05-05 19:43:54 +00:00
}
// 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
}
2017-04-20 10:57:07 +00:00
fsGroups := [ ] types . UnixGroupID { }
2016-05-05 19:43:54 +00:00
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 ) ... )
2016-08-23 16:52:27 +00:00
allErrs = append ( allErrs , s . strategies . SeccompStrategy . ValidatePod ( pod ) ... )
2016-05-05 19:43:54 +00:00
// 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" ) )
}
2016-08-19 08:33:56 +00:00
allErrs = append ( allErrs , s . strategies . SysctlsStrategy . Validate ( pod ) ... )
2016-09-01 22:55:34 +00:00
// TODO(timstclair): ValidatePodSecurityContext should be renamed to ValidatePod since its scope
// is not limited to the PodSecurityContext.
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 ( field . NewPath ( "spec" , "volumes" ) . Index ( i ) , string ( fsType ) , err . Error ( ) ) )
continue
}
if ! 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 ) ) ) )
2017-04-01 13:12:21 +00:00
continue
}
if fsType == extensions . HostPath {
if ! psputil . PSPAllowsHostVolumePath ( s . psp , v . HostPath . Path ) {
allErrs = append ( allErrs , field . Invalid (
field . NewPath ( "spec" , "volumes" ) . Index ( i ) , string ( fsType ) ,
fmt . Sprintf ( "host path %s is not allowed to be used. allowed host paths: %v" , v . HostPath . Path , s . psp . Spec . AllowedHostPaths ) ) )
}
2016-09-01 22:55:34 +00:00
}
}
}
2016-05-05 19:43:54 +00:00
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 ) ... )
2016-08-18 00:24:47 +00:00
allErrs = append ( allErrs , s . strategies . AppArmorStrategy . Validate ( pod , container ) ... )
2016-08-23 16:52:27 +00:00
allErrs = append ( allErrs , s . strategies . SeccompStrategy . ValidateContainer ( pod , container ) ... )
2016-05-05 19:43:54 +00:00
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 ! 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 ) ... )
}
2016-07-21 02:20:42 +00:00
containersPath = fldPath . Child ( "initContainers" )
for idx , c := range pod . Spec . InitContainers {
idxPath := containersPath . Index ( idx )
allErrs = append ( allErrs , s . hasInvalidHostPort ( & c , idxPath ) ... )
}
2016-05-05 19:43:54 +00:00
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
}