Commit 39377837 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #50212 from jhorwit2/jah/psp-hostpath

Automatic merge from submit-queue (batch tested with PRs 50719, 51216, 50212, 51408, 51381) Allow PSP's to specify a whitelist of allowed paths for host volume **What this PR does / why we need it**: Reverts the revert for the allowed host path feature that was moved from v1.7 to v1.8. This PR also changes the api as discussed in https://github.com/kubernetes/kubernetes/pull/47811. Original pr: https://github.com/kubernetes/kubernetes/pull/43946 revert: https://github.com/kubernetes/kubernetes/pull/47851 **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #29326 **Special notes for your reviewer**: cc @liggitt @ericchiang @php-coder It seems the api build process has changed. Hopefully I did it right 👼 . **Release note**: ```release-note Allow PSP's to specify a whitelist of allowed paths for host volume ```
parents 23ba6038 6ec738a8
......@@ -62428,6 +62428,15 @@
}
}
},
"io.k8s.api.extensions.v1beta1.AllowedHostPath": {
"description": "defines the host volume conditions that will be enabled by a policy for pods to use. It requires the path prefix to be defined.",
"properties": {
"pathPrefix": {
"description": "is the path prefix that the host volume must match. It does not support `*`. Trailing slashes are trimmed when validating the path prefix with a host path.\n\nExamples: `/foo` would allow `/foo`, `/foo/` and `/foo/bar` `/foo` would not allow `/food` or `/etc/foo`",
"type": "string"
}
}
},
"io.k8s.api.extensions.v1beta1.DaemonSet": {
"description": "DaemonSet represents the configuration of a daemon set.",
"properties": {
......@@ -63302,6 +63311,13 @@
"type": "string"
}
},
"allowedHostPaths": {
"description": "is a white list of allowed host paths. Empty indicates that all host paths may be used.",
"type": "array",
"items": {
"$ref": "#/definitions/io.k8s.api.extensions.v1beta1.AllowedHostPath"
}
},
"defaultAddCapabilities": {
"description": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capabiility in both DefaultAddCapabilities and RequiredDropCapabilities.",
"type": "array",
......@@ -9470,6 +9470,13 @@
"allowPrivilegeEscalation": {
"type": "boolean",
"description": "AllowPrivilegeEscalation determines if a pod can request to allow privilege escalation."
},
"allowedHostPaths": {
"type": "array",
"items": {
"$ref": "v1beta1.AllowedHostPath"
},
"description": "is a white list of allowed host paths. Empty indicates that all host paths may be used."
}
}
},
......@@ -9588,6 +9595,16 @@
}
}
},
"v1beta1.AllowedHostPath": {
"id": "v1beta1.AllowedHostPath",
"description": "defines the host volume conditions that will be enabled by a policy for pods to use. It requires the path prefix to be defined.",
"properties": {
"pathPrefix": {
"type": "string",
"description": "is the path prefix that the host volume must match. It does not support `*`. Trailing slashes are trimmed when validating the path prefix with a host path.\n\nExamples: `/foo` would allow `/foo`, `/foo/` and `/foo/bar` `/foo` would not allow `/food` or `/etc/foo`"
}
}
},
"v1beta1.ReplicaSetList": {
"id": "v1beta1.ReplicaSetList",
"description": "ReplicaSetList is a collection of ReplicaSets.",
......
......@@ -419,6 +419,42 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<h2 id="_definitions">Definitions</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_v1beta1_allowedhostpath">v1beta1.AllowedHostPath</h3>
<div class="paragraph">
<p>defines the host volume conditions that will be enabled by a policy for pods to use. It requires the path prefix to be defined.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">pathPrefix</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">is the path prefix that the host volume must match. It does not support <code>*</code>. Trailing slashes are trimmed when validating the path prefix with a host path.<br>
<br>
Examples: <code>/foo</code> would allow <code>/foo</code>, <code>/foo/</code> and <code>/foo/bar</code> <code>/foo</code> would not allow <code>/food</code> or <code>/etc/foo</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1beta1_deploymentstatus">v1beta1.DeploymentStatus</h3>
<div class="paragraph">
<p>DeploymentStatus is the most recently observed status of the Deployment.</p>
......@@ -7691,6 +7727,13 @@ Both these may change in the future. Incoming requests are matched against the h
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">allowedHostPaths</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">is a white list of allowed host paths. Empty indicates that all host paths may be used.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1beta1_allowedhostpath">v1beta1.AllowedHostPath</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
......
......@@ -933,6 +933,22 @@ type PodSecurityPolicySpec struct {
// privilege escalation.
// +optional
AllowPrivilegeEscalation bool
// AllowedHostPaths is a white list of allowed host paths. Empty indicates that all host paths may be used.
// +optional
AllowedHostPaths []AllowedHostPath
}
// AllowedHostPath defines the host volume conditions that will be enabled by a policy
// for pods to use. It requires the path prefix to be defined.
type AllowedHostPath struct {
// PathPrefix is the path prefix that the host volume must match.
// PathPrefix does not support `*`.
// Trailing slashes are trimmed when validating the path prefix with a host path.
//
// Examples:
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo`
PathPrefix string
}
// HostPortRange defines a range of host ports that will be enabled by a policy
......
......@@ -42,6 +42,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
return scheme.AddGeneratedConversionFuncs(
Convert_v1beta1_APIVersion_To_extensions_APIVersion,
Convert_extensions_APIVersion_To_v1beta1_APIVersion,
Convert_v1beta1_AllowedHostPath_To_extensions_AllowedHostPath,
Convert_extensions_AllowedHostPath_To_v1beta1_AllowedHostPath,
Convert_v1beta1_CustomMetricCurrentStatus_To_extensions_CustomMetricCurrentStatus,
Convert_extensions_CustomMetricCurrentStatus_To_v1beta1_CustomMetricCurrentStatus,
Convert_v1beta1_CustomMetricCurrentStatusList_To_extensions_CustomMetricCurrentStatusList,
......@@ -165,6 +167,26 @@ func Convert_extensions_APIVersion_To_v1beta1_APIVersion(in *extensions.APIVersi
return autoConvert_extensions_APIVersion_To_v1beta1_APIVersion(in, out, s)
}
func autoConvert_v1beta1_AllowedHostPath_To_extensions_AllowedHostPath(in *v1beta1.AllowedHostPath, out *extensions.AllowedHostPath, s conversion.Scope) error {
out.PathPrefix = in.PathPrefix
return nil
}
// Convert_v1beta1_AllowedHostPath_To_extensions_AllowedHostPath is an autogenerated conversion function.
func Convert_v1beta1_AllowedHostPath_To_extensions_AllowedHostPath(in *v1beta1.AllowedHostPath, out *extensions.AllowedHostPath, s conversion.Scope) error {
return autoConvert_v1beta1_AllowedHostPath_To_extensions_AllowedHostPath(in, out, s)
}
func autoConvert_extensions_AllowedHostPath_To_v1beta1_AllowedHostPath(in *extensions.AllowedHostPath, out *v1beta1.AllowedHostPath, s conversion.Scope) error {
out.PathPrefix = in.PathPrefix
return nil
}
// Convert_extensions_AllowedHostPath_To_v1beta1_AllowedHostPath is an autogenerated conversion function.
func Convert_extensions_AllowedHostPath_To_v1beta1_AllowedHostPath(in *extensions.AllowedHostPath, out *v1beta1.AllowedHostPath, s conversion.Scope) error {
return autoConvert_extensions_AllowedHostPath_To_v1beta1_AllowedHostPath(in, out, s)
}
func autoConvert_v1beta1_CustomMetricCurrentStatus_To_extensions_CustomMetricCurrentStatus(in *v1beta1.CustomMetricCurrentStatus, out *extensions.CustomMetricCurrentStatus, s conversion.Scope) error {
out.Name = in.Name
out.CurrentValue = in.CurrentValue
......@@ -1054,6 +1076,7 @@ func autoConvert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySp
out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem
out.DefaultAllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.DefaultAllowPrivilegeEscalation))
out.AllowPrivilegeEscalation = in.AllowPrivilegeEscalation
out.AllowedHostPaths = *(*[]extensions.AllowedHostPath)(unsafe.Pointer(&in.AllowedHostPaths))
return nil
}
......@@ -1097,6 +1120,7 @@ func autoConvert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySp
out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem
out.DefaultAllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.DefaultAllowPrivilegeEscalation))
out.AllowPrivilegeEscalation = in.AllowPrivilegeEscalation
out.AllowedHostPaths = *(*[]v1beta1.AllowedHostPath)(unsafe.Pointer(&in.AllowedHostPaths))
return nil
}
......
......@@ -19,6 +19,8 @@ package validation
import (
"fmt"
"net"
"path/filepath"
"regexp"
"strconv"
"strings"
......@@ -661,6 +663,7 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa
allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.DefaultAddCapabilities, field.NewPath("defaultAddCapabilities"))...)
allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.AllowedCapabilities, field.NewPath("allowedCapabilities"))...)
allErrs = append(allErrs, validatePSPDefaultAllowPrivilegeEscalation(fldPath.Child("defaultAllowPrivilegeEscalation"), spec.DefaultAllowPrivilegeEscalation, spec.AllowPrivilegeEscalation)...)
allErrs = append(allErrs, validatePSPAllowedHostPaths(fldPath.Child("allowedHostPaths"), spec.AllowedHostPaths)...)
return allErrs
}
......@@ -701,6 +704,29 @@ func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string,
return allErrs
}
// validatePSPAllowedHostPaths makes sure all allowed host paths follow:
// 1. path prefix is required
// 2. path prefix does not have any element which is ".."
func validatePSPAllowedHostPaths(fldPath *field.Path, allowedHostPaths []extensions.AllowedHostPath) field.ErrorList {
allErrs := field.ErrorList{}
for i, target := range allowedHostPaths {
if target.PathPrefix == "" {
allErrs = append(allErrs, field.Required(fldPath.Index(i), "is required"))
break
}
parts := strings.Split(filepath.ToSlash(target.PathPrefix), "/")
for _, item := range parts {
if item == ".." {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), target.PathPrefix, "must not contain '..'"))
break // even for `../../..`, one error is sufficient to make the point
}
}
}
return allErrs
}
// validatePSPSELinux validates the SELinux fields of PodSecurityPolicy.
func validatePSPSELinux(fldPath *field.Path, seLinux *extensions.SELinuxStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}
......
......@@ -2418,6 +2418,10 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
Rule: extensions.SupplementalGroupsStrategyRunAsAny,
},
AllowedHostPaths: []extensions.AllowedHostPath{
{PathPrefix: "/foo/bar"},
{PathPrefix: "/baz/"},
},
},
}
}
......@@ -2497,6 +2501,16 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
seccomp.AllowedProfilesAnnotationKey: "docker/default,not-good",
}
invalidAllowedHostPathMissingPath := validPSP()
invalidAllowedHostPathMissingPath.Spec.AllowedHostPaths = []extensions.AllowedHostPath{
{PathPrefix: ""},
}
invalidAllowedHostPathBacksteps := validPSP()
invalidAllowedHostPathBacksteps.Spec.AllowedHostPaths = []extensions.AllowedHostPath{
{PathPrefix: "/dont/allow/backsteps/.."},
}
invalidDefaultAllowPrivilegeEscalation := validPSP()
pe := true
invalidDefaultAllowPrivilegeEscalation.Spec.DefaultAllowPrivilegeEscalation = &pe
......@@ -2612,6 +2626,16 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
errorType: field.ErrorTypeInvalid,
errorDetail: "Cannot set DefaultAllowPrivilegeEscalation to true without also setting AllowPrivilegeEscalation to true",
},
"invalid allowed host path empty path": {
psp: invalidAllowedHostPathMissingPath,
errorType: field.ErrorTypeRequired,
errorDetail: "is required",
},
"invalid allowed host path with backsteps": {
psp: invalidAllowedHostPathBacksteps,
errorType: field.ErrorTypeInvalid,
errorDetail: "must not contain '..'",
},
}
for k, v := range errorCases {
......
......@@ -43,6 +43,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
return nil
}, InType: reflect.TypeOf(&APIVersion{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*AllowedHostPath).DeepCopyInto(out.(*AllowedHostPath))
return nil
}, InType: reflect.TypeOf(&AllowedHostPath{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*CustomMetricCurrentStatus).DeepCopyInto(out.(*CustomMetricCurrentStatus))
return nil
}, InType: reflect.TypeOf(&CustomMetricCurrentStatus{})},
......@@ -270,6 +274,22 @@ func (in *APIVersion) DeepCopy() *APIVersion {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AllowedHostPath) DeepCopyInto(out *AllowedHostPath) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedHostPath.
func (in *AllowedHostPath) DeepCopy() *AllowedHostPath {
if in == nil {
return nil
}
out := new(AllowedHostPath)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomMetricCurrentStatus) DeepCopyInto(out *CustomMetricCurrentStatus) {
*out = *in
out.CurrentValue = in.CurrentValue.DeepCopy()
......@@ -1117,6 +1137,11 @@ func (in *PodSecurityPolicySpec) DeepCopyInto(out *PodSecurityPolicySpec) {
**out = **in
}
}
if in.AllowedHostPaths != nil {
in, out := &in.AllowedHostPaths, &out.AllowedHostPaths
*out = make([]AllowedHostPath, len(*in))
copy(*out, *in)
}
return
}
......
......@@ -239,7 +239,8 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
// TODO(tallclair): 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) {
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)
......@@ -248,10 +249,19 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
continue
}
if !allowedVolumes.Has(string(fsType)) {
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
}
if fsType == extensions.HostPath {
if !psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path) {
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")))
}
}
}
}
......
......@@ -248,6 +248,23 @@ 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 = []extensions.AllowedHostPath{
{PathPrefix: "/foo/bar"},
}
failOtherSysctlsAllowedPSP := defaultPSP()
failOtherSysctlsAllowedPSP.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "bar,abc"
......@@ -318,6 +335,11 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
psp: defaultPSP(),
expectedError: "hostPath volumes are not allowed to be used",
},
"failHostPathDirPSP": {
pod: failHostPathDirPod,
psp: failHostPathDirPSP,
expectedError: "is not allowed to be used",
},
"failSafeSysctlFooPod with failNoSysctlAllowedSCC": {
pod: failSafeSysctlFooPod,
psp: failNoSysctlAllowedPSP,
......@@ -556,6 +578,30 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
Level: "level",
}
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 = []extensions.AllowedHostPath{
{PathPrefix: "/foo/bar"},
}
hostPathDirAsterisksPSP := defaultPSP()
hostPathDirAsterisksPSP.Spec.Volumes = []extensions.FSType{extensions.All}
hostPathDirAsterisksPSP.Spec.AllowedHostPaths = []extensions.AllowedHostPath{
{PathPrefix: "/foo/bar"},
}
sysctlAllowFooPSP := defaultPSP()
sysctlAllowFooPSP.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "foo"
......@@ -619,6 +665,14 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
pod: unsafeSysctlFooPod,
psp: defaultPSP(),
},
"pass hostDir allowed directory validating PSP": {
pod: hostPathDirPod,
psp: hostPathDirPSP,
},
"pass hostDir all volumes allowed validating PSP": {
pod: hostPathDirPod,
psp: hostPathDirAsterisksPSP,
},
"pass seccomp validating PSP": {
pod: seccompPod,
psp: seccompPSP,
......
......@@ -18,6 +18,7 @@ package util
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api"
......@@ -170,3 +171,53 @@ func UserFallsInRange(id int64, rng extensions.UserIDRange) bool {
func GroupFallsInRange(id int64, rng extensions.GroupIDRange) bool {
return id >= rng.Min && id <= rng.Max
}
// AllowsHostVolumePath 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 AllowsHostVolumePath(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.PathPrefix) {
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
// Example where this check applies: s=/foo/bar and pathPrefix=/foo
return true
}
return false
}
......@@ -103,3 +103,95 @@ func TestPSPAllowsFSType(t *testing.T) {
}
}
}
func TestAllowsHostVolumePath(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: []extensions.AllowedHostPath{
{PathPrefix: "/foo"},
},
},
},
path: "/foobar",
allows: false,
},
"match on direct match": {
psp: &extensions.PodSecurityPolicy{
Spec: extensions.PodSecurityPolicySpec{
AllowedHostPaths: []extensions.AllowedHostPath{
{PathPrefix: "/foo"},
},
},
},
path: "/foo",
allows: true,
},
"match with trailing slash on host path": {
psp: &extensions.PodSecurityPolicy{
Spec: extensions.PodSecurityPolicySpec{
AllowedHostPaths: []extensions.AllowedHostPath{
{PathPrefix: "/foo"},
},
},
},
path: "/foo/",
allows: true,
},
"match with trailing slash on allowed path": {
psp: &extensions.PodSecurityPolicy{
Spec: extensions.PodSecurityPolicySpec{
AllowedHostPaths: []extensions.AllowedHostPath{
{PathPrefix: "/foo/"},
},
},
},
path: "/foo",
allows: true,
},
"match child directory": {
psp: &extensions.PodSecurityPolicy{
Spec: extensions.PodSecurityPolicySpec{
AllowedHostPaths: []extensions.AllowedHostPath{
{PathPrefix: "/foo/"},
},
},
},
path: "/foo/bar",
allows: true,
},
"non-matching parent directory": {
psp: &extensions.PodSecurityPolicy{
Spec: extensions.PodSecurityPolicySpec{
AllowedHostPaths: []extensions.AllowedHostPath{
{PathPrefix: "/foo/bar"},
},
},
},
path: "/foo",
allows: false,
},
}
for k, v := range tests {
allows := AllowsHostVolumePath(v.psp, v.path)
if v.allows != allows {
t.Errorf("%s expected %t but got %t", k, v.allows, allows)
}
}
}
......@@ -39,6 +39,19 @@ message APIVersion {
optional string name = 1;
}
// defines the host volume conditions that will be enabled by a policy
// for pods to use. It requires the path prefix to be defined.
message AllowedHostPath {
// is the path prefix that the host volume must match.
// It does not support `*`.
// Trailing slashes are trimmed when validating the path prefix with a host path.
//
// Examples:
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo`
optional string pathPrefix = 1;
}
message CustomMetricCurrentStatus {
// Custom Metric name.
optional string name = 1;
......@@ -755,6 +768,10 @@ message PodSecurityPolicySpec {
// privilege escalation.
// +optional
optional bool allowPrivilegeEscalation = 16;
// is a white list of allowed host paths. Empty indicates that all host paths may be used.
// +optional
repeated AllowedHostPath allowedHostPaths = 17;
}
// ReplicaSet represents the configuration of a ReplicaSet.
......
......@@ -966,6 +966,22 @@ type PodSecurityPolicySpec struct {
// privilege escalation.
// +optional
AllowPrivilegeEscalation bool `json:"allowPrivilegeEscalation,omitempty" protobuf:"varint,16,opt,name=allowPrivilegeEscalation"`
// is a white list of allowed host paths. Empty indicates that all host paths may be used.
// +optional
AllowedHostPaths []AllowedHostPath `json:"allowedHostPaths,omitempty" protobuf:"bytes,17,rep,name=allowedHostPaths"`
}
// defines the host volume conditions that will be enabled by a policy
// for pods to use. It requires the path prefix to be defined.
type AllowedHostPath struct {
// is the path prefix that the host volume must match.
// It does not support `*`.
// Trailing slashes are trimmed when validating the path prefix with a host path.
//
// Examples:
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo`
PathPrefix string `json:"pathPrefix,omitempty" protobuf:"bytes,1,rep,name=pathPrefix"`
}
// FS Type gives strong typing to different file systems that are used by volumes.
......
......@@ -36,6 +36,15 @@ func (APIVersion) SwaggerDoc() map[string]string {
return map_APIVersion
}
var map_AllowedHostPath = map[string]string{
"": "defines the host volume conditions that will be enabled by a policy for pods to use. It requires the path prefix to be defined.",
"pathPrefix": "is the path prefix that the host volume must match. It does not support `*`. Trailing slashes are trimmed when validating the path prefix with a host path.\n\nExamples: `/foo` would allow `/foo`, `/foo/` and `/foo/bar` `/foo` would not allow `/food` or `/etc/foo`",
}
func (AllowedHostPath) SwaggerDoc() map[string]string {
return map_AllowedHostPath
}
var map_CustomMetricCurrentStatus = map[string]string{
"name": "Custom Metric name.",
"value": "Custom Metric value (average).",
......@@ -438,6 +447,7 @@ var map_PodSecurityPolicySpec = map[string]string{
"readOnlyRootFilesystem": "ReadOnlyRootFilesystem when set to true will force containers to run with a read only root file system. If the container specifically requests to run with a non-read only root file system the PSP should deny the pod. If set to false the container may run with a read only root file system if it wishes but it will not be forced to.",
"defaultAllowPrivilegeEscalation": "DefaultAllowPrivilegeEscalation controls the default setting for whether a process can gain more privileges than its parent process.",
"allowPrivilegeEscalation": "AllowPrivilegeEscalation determines if a pod can request to allow privilege escalation.",
"allowedHostPaths": "is a white list of allowed host paths. Empty indicates that all host paths may be used.",
}
func (PodSecurityPolicySpec) SwaggerDoc() map[string]string {
......
......@@ -44,6 +44,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
return nil
}, InType: reflect.TypeOf(&APIVersion{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*AllowedHostPath).DeepCopyInto(out.(*AllowedHostPath))
return nil
}, InType: reflect.TypeOf(&AllowedHostPath{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*CustomMetricCurrentStatus).DeepCopyInto(out.(*CustomMetricCurrentStatus))
return nil
}, InType: reflect.TypeOf(&CustomMetricCurrentStatus{})},
......@@ -295,6 +299,22 @@ func (in *APIVersion) DeepCopy() *APIVersion {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AllowedHostPath) DeepCopyInto(out *AllowedHostPath) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedHostPath.
func (in *AllowedHostPath) DeepCopy() *AllowedHostPath {
if in == nil {
return nil
}
out := new(AllowedHostPath)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomMetricCurrentStatus) DeepCopyInto(out *CustomMetricCurrentStatus) {
*out = *in
out.CurrentValue = in.CurrentValue.DeepCopy()
......@@ -1365,6 +1385,11 @@ func (in *PodSecurityPolicySpec) DeepCopyInto(out *PodSecurityPolicySpec) {
**out = **in
}
}
if in.AllowedHostPaths != nil {
in, out := &in.AllowedHostPaths, &out.AllowedHostPaths
*out = make([]AllowedHostPath, len(*in))
copy(*out, *in)
}
return
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment