mirror of https://github.com/k3s-io/k3s
Added host path whitelist to psp
parent
52337d5db6
commit
f75b3f3d05
|
@ -884,6 +884,10 @@ type PodSecurityPolicySpec struct {
|
|||
// will not be forced to.
|
||||
// +optional
|
||||
ReadOnlyRootFilesystem bool
|
||||
// AllowedHostPaths is a white list of allowed host path prefixes. Empty indicates that all
|
||||
// host paths may be used.
|
||||
// +optional
|
||||
AllowedHostPaths []string
|
||||
}
|
||||
|
||||
// HostPortRange defines a range of host ports that will be enabled by a policy
|
||||
|
|
|
@ -909,6 +909,10 @@ type PodSecurityPolicySpec struct {
|
|||
// will not be forced to.
|
||||
// +optional
|
||||
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty" protobuf:"varint,14,opt,name=readOnlyRootFilesystem"`
|
||||
// AllowedHostPaths is a white list of allowed host path prefixes. Empty indicates that all
|
||||
// host paths may be used.
|
||||
// +optional
|
||||
AllowedHostPaths []string `json:"allowedHostPaths,omitempty" protobuf:"bytes,15,opt,name=allowedHostPaths"`
|
||||
}
|
||||
|
||||
// FS Type gives strong typing to different file systems that are used by volumes.
|
||||
|
|
|
@ -241,6 +241,15 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
|
|||
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
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,6 +238,21 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
failHostPathDirPod := defaultPod()
|
||||
failHostPathDirPod.Spec.Volumes = []api.Volume{
|
||||
{
|
||||
Name: "bad volume",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{
|
||||
Path: "/fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
failHostPathDirPSP := defaultPSP()
|
||||
failHostPathDirPSP.Spec.Volumes = []extensions.FSType{extensions.HostPath}
|
||||
failHostPathDirPSP.Spec.AllowedHostPaths = []string{"/foo/bar"}
|
||||
|
||||
failOtherSysctlsAllowedPSP := defaultPSP()
|
||||
failOtherSysctlsAllowedPSP.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "bar,abc"
|
||||
|
||||
|
@ -308,6 +323,11 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||
psp: defaultPSP(),
|
||||
expectedError: "hostPath volumes are not allowed to be used",
|
||||
},
|
||||
"failHostPathDirPSP": {
|
||||
pod: failHostPathDirPod,
|
||||
psp: failHostPathDirPSP,
|
||||
expectedError: "host path /fail is not allowed to be used. allowed host paths: [/foo/bar]",
|
||||
},
|
||||
"failSafeSysctlFooPod with failNoSysctlAllowedSCC": {
|
||||
pod: failSafeSysctlFooPod,
|
||||
psp: failNoSysctlAllowedPSP,
|
||||
|
@ -706,13 +726,28 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
|
|||
hostDirPod := defaultPod()
|
||||
hostDirPod.Spec.Volumes = []api.Volume{
|
||||
{
|
||||
Name: "bad volume",
|
||||
Name: "good volume",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hostPathDirPod := defaultPod()
|
||||
hostPathDirPod.Spec.Volumes = []api.Volume{
|
||||
{
|
||||
Name: "good volume",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{
|
||||
Path: "/foo/bar/baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
hostPathDirPSP := defaultPSP()
|
||||
hostPathDirPSP.Spec.Volumes = []extensions.FSType{extensions.HostPath}
|
||||
hostPathDirPSP.Spec.AllowedHostPaths = []string{"/foo/bar"}
|
||||
|
||||
hostPortPSP := defaultPSP()
|
||||
hostPortPSP.Spec.HostPorts = []extensions.HostPortRange{{Min: 1, Max: 1}}
|
||||
hostPortPod := defaultPod()
|
||||
|
@ -773,6 +808,10 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
|
|||
pod: hostDirPod,
|
||||
psp: hostDirPSP,
|
||||
},
|
||||
"pass hostDir allowed directory validating PSP": {
|
||||
pod: hostPathDirPod,
|
||||
psp: hostPathDirPSP,
|
||||
},
|
||||
"pass hostPort validating PSP": {
|
||||
pod: hostPortPod,
|
||||
psp: hostPortPSP,
|
||||
|
|
|
@ -18,6 +18,7 @@ package util
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
@ -168,3 +169,52 @@ func UserFallsInRange(id types.UnixUserID, rng extensions.UserIDRange) bool {
|
|||
func GroupFallsInRange(id types.UnixGroupID, rng extensions.GroupIDRange) bool {
|
||||
return id >= rng.Min && id <= rng.Max
|
||||
}
|
||||
|
||||
// PSPAllowsHostVolumePath is a utility for checking if a PSP allows the host volume path.
|
||||
// This only checks the path. You should still check to make sure the host volume fs type is allowed.
|
||||
func PSPAllowsHostVolumePath(psp *extensions.PodSecurityPolicy, hostPath string) bool {
|
||||
if psp == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If no allowed paths are specified then allow any path
|
||||
if len(psp.Spec.AllowedHostPaths) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, allowedPath := range psp.Spec.AllowedHostPaths {
|
||||
if hasPathPrefix(hostPath, allowedPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPathPrefix returns true if the string matches pathPrefix exactly, or if is prefixed with pathPrefix at a path segment boundary
|
||||
// the string and pathPrefix are both normalized to remove trailing slashes prior to checking.
|
||||
func hasPathPrefix(s, pathPrefix string) bool {
|
||||
|
||||
s = strings.TrimSuffix(s, "/")
|
||||
pathPrefix = strings.TrimSuffix(pathPrefix, "/")
|
||||
|
||||
// Short circuit if s doesn't contain the prefix at all
|
||||
if !strings.HasPrefix(s, pathPrefix) {
|
||||
return false
|
||||
}
|
||||
|
||||
pathPrefixLength := len(pathPrefix)
|
||||
|
||||
if len(s) == pathPrefixLength {
|
||||
// Exact match
|
||||
return true
|
||||
}
|
||||
|
||||
if s[pathPrefixLength:pathPrefixLength+1] == "/" {
|
||||
// The next character in s is a path segment boundary
|
||||
// Check this instead of normalizing pathPrefix to avoid allocating on every call
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -103,3 +103,83 @@ func TestPSPAllowsFSType(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPSPAllowsHostVolumePath(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
psp *extensions.PodSecurityPolicy
|
||||
path string
|
||||
allows bool
|
||||
}{
|
||||
"nil psp": {
|
||||
psp: nil,
|
||||
path: "/test",
|
||||
allows: false,
|
||||
},
|
||||
"empty allowed paths": {
|
||||
psp: &extensions.PodSecurityPolicy{},
|
||||
path: "/test",
|
||||
allows: true,
|
||||
},
|
||||
"non-matching": {
|
||||
psp: &extensions.PodSecurityPolicy{
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
AllowedHostPaths: []string{"/foo"},
|
||||
},
|
||||
},
|
||||
path: "/foobar",
|
||||
allows: false,
|
||||
},
|
||||
"match on direct match": {
|
||||
psp: &extensions.PodSecurityPolicy{
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
AllowedHostPaths: []string{"/foo"},
|
||||
},
|
||||
},
|
||||
path: "/foo",
|
||||
allows: true,
|
||||
},
|
||||
"match with trailing slash on host path": {
|
||||
psp: &extensions.PodSecurityPolicy{
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
AllowedHostPaths: []string{"/foo"},
|
||||
},
|
||||
},
|
||||
path: "/foo/",
|
||||
allows: true,
|
||||
},
|
||||
"match with trailing slash on allowed path": {
|
||||
psp: &extensions.PodSecurityPolicy{
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
AllowedHostPaths: []string{"/foo/"},
|
||||
},
|
||||
},
|
||||
path: "/foo",
|
||||
allows: true,
|
||||
},
|
||||
"match child directory": {
|
||||
psp: &extensions.PodSecurityPolicy{
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
AllowedHostPaths: []string{"/foo/"},
|
||||
},
|
||||
},
|
||||
path: "/foo/bar",
|
||||
allows: true,
|
||||
},
|
||||
"non-matching parent directory": {
|
||||
psp: &extensions.PodSecurityPolicy{
|
||||
Spec: extensions.PodSecurityPolicySpec{
|
||||
AllowedHostPaths: []string{"/foo/bar"},
|
||||
},
|
||||
},
|
||||
path: "/foo",
|
||||
allows: false,
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range tests {
|
||||
allows := PSPAllowsHostVolumePath(v.psp, v.path)
|
||||
if v.allows != allows {
|
||||
t.Errorf("%s expected PSPAllowsHostVolumePath to return %t but got %t", k, v.allows, allows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue