Unverified Commit f7310106 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #58647 from oracle/for/upstream/master/hostpath-psp-readonly

Automatic merge from submit-queue (batch tested with PRs 64344, 64709, 64717, 63631, 58647). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Add support for enforcing read only host paths in PSPs. **What this PR does / why we need it**: This PR adds support for the PSP to enforce that host paths are readonly. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes #57371 xref https://github.com/kubernetes/features/issues/5 **Special notes for your reviewer**: **Release note**: ```release-note PodSecurityPolicy now supports restricting hostPath volume mounts to be readOnly and under specific path prefixes ``` /cc @ericchiang @liggitt
parents 0bd77a28 c7fbcf35
...@@ -81376,6 +81376,10 @@ ...@@ -81376,6 +81376,10 @@
"pathPrefix": { "pathPrefix": {
"description": "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`", "description": "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`",
"type": "string" "type": "string"
},
"readOnly": {
"description": "when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.",
"type": "boolean"
} }
} }
}, },
...@@ -82920,6 +82924,10 @@ ...@@ -82920,6 +82924,10 @@
"pathPrefix": { "pathPrefix": {
"description": "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`", "description": "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`",
"type": "string" "type": "string"
},
"readOnly": {
"description": "when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.",
"type": "boolean"
} }
} }
}, },
...@@ -10517,6 +10517,10 @@ ...@@ -10517,6 +10517,10 @@
"pathPrefix": { "pathPrefix": {
"type": "string", "type": "string",
"description": "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`" "description": "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`"
},
"readOnly": {
"type": "boolean",
"description": "when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly."
} }
} }
}, },
......
...@@ -2650,6 +2650,10 @@ ...@@ -2650,6 +2650,10 @@
"pathPrefix": { "pathPrefix": {
"type": "string", "type": "string",
"description": "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`" "description": "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`"
},
"readOnly": {
"type": "boolean",
"description": "when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly."
} }
} }
}, },
......
...@@ -450,6 +450,13 @@ Examples: <code>/foo</code> would allow <code>/foo</code>, <code>/foo/</code> an ...@@ -450,6 +450,13 @@ Examples: <code>/foo</code> would allow <code>/foo</code>, <code>/foo/</code> an
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.</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">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
</tbody> </tbody>
</table> </table>
......
...@@ -420,6 +420,13 @@ Examples: <code>/foo</code> would allow <code>/foo</code>, <code>/foo/</code> an ...@@ -420,6 +420,13 @@ Examples: <code>/foo</code> would allow <code>/foo</code>, <code>/foo/</code> an
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.</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">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
</tbody> </tbody>
</table> </table>
......
...@@ -168,6 +168,7 @@ func Convert_policy_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume(in *policy.Al ...@@ -168,6 +168,7 @@ func Convert_policy_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume(in *policy.Al
func autoConvert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.AllowedHostPath, out *policy.AllowedHostPath, s conversion.Scope) error { func autoConvert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.AllowedHostPath, out *policy.AllowedHostPath, s conversion.Scope) error {
out.PathPrefix = in.PathPrefix out.PathPrefix = in.PathPrefix
out.ReadOnly = in.ReadOnly
return nil return nil
} }
...@@ -178,6 +179,7 @@ func Convert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.Allow ...@@ -178,6 +179,7 @@ func Convert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.Allow
func autoConvert_policy_AllowedHostPath_To_v1beta1_AllowedHostPath(in *policy.AllowedHostPath, out *v1beta1.AllowedHostPath, s conversion.Scope) error { func autoConvert_policy_AllowedHostPath_To_v1beta1_AllowedHostPath(in *policy.AllowedHostPath, out *v1beta1.AllowedHostPath, s conversion.Scope) error {
out.PathPrefix = in.PathPrefix out.PathPrefix = in.PathPrefix
out.ReadOnly = in.ReadOnly
return nil return nil
} }
......
...@@ -228,6 +228,9 @@ type AllowedHostPath struct { ...@@ -228,6 +228,9 @@ type AllowedHostPath struct {
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar` // `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo` // `/foo` would not allow `/food` or `/etc/foo`
PathPrefix string PathPrefix string
// when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.
ReadOnly bool
} }
// HostPortRange defines a range of host ports that will be enabled by a policy // HostPortRange defines a range of host ports that will be enabled by a policy
......
...@@ -98,6 +98,7 @@ func Convert_policy_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume(in *policy.Al ...@@ -98,6 +98,7 @@ func Convert_policy_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume(in *policy.Al
func autoConvert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.AllowedHostPath, out *policy.AllowedHostPath, s conversion.Scope) error { func autoConvert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.AllowedHostPath, out *policy.AllowedHostPath, s conversion.Scope) error {
out.PathPrefix = in.PathPrefix out.PathPrefix = in.PathPrefix
out.ReadOnly = in.ReadOnly
return nil return nil
} }
...@@ -108,6 +109,7 @@ func Convert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.Allow ...@@ -108,6 +109,7 @@ func Convert_v1beta1_AllowedHostPath_To_policy_AllowedHostPath(in *v1beta1.Allow
func autoConvert_policy_AllowedHostPath_To_v1beta1_AllowedHostPath(in *policy.AllowedHostPath, out *v1beta1.AllowedHostPath, s conversion.Scope) error { func autoConvert_policy_AllowedHostPath_To_v1beta1_AllowedHostPath(in *policy.AllowedHostPath, out *v1beta1.AllowedHostPath, s conversion.Scope) error {
out.PathPrefix = in.PathPrefix out.PathPrefix = in.PathPrefix
out.ReadOnly = in.ReadOnly
return nil return nil
} }
......
...@@ -227,10 +227,33 @@ func (s *simpleProvider) ValidatePod(pod *api.Pod, fldPath *field.Path) field.Er ...@@ -227,10 +227,33 @@ func (s *simpleProvider) ValidatePod(pod *api.Pod, fldPath *field.Path) field.Er
} }
if fsType == policy.HostPath { if fsType == policy.HostPath {
if !psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path) { allows, mustBeReadOnly := psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path)
if !allows {
allErrs = append(allErrs, field.Invalid( allErrs = append(allErrs, field.Invalid(
field.NewPath("spec", "volumes").Index(i).Child("hostPath", "pathPrefix"), v.HostPath.Path, field.NewPath("spec", "volumes").Index(i).Child("hostPath", "pathPrefix"), v.HostPath.Path,
fmt.Sprintf("is not allowed to be used"))) 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"),
)
}
}
}
} }
} }
......
...@@ -241,6 +241,32 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { ...@@ -241,6 +241,32 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
{PathPrefix: "/foo/bar"}, {PathPrefix: "/foo/bar"},
} }
failHostPathReadOnlyPod := defaultPod()
failHostPathReadOnlyPod.Spec.Containers[0].VolumeMounts = []api.VolumeMount{
{
Name: "bad volume",
ReadOnly: false,
},
}
failHostPathReadOnlyPod.Spec.Volumes = []api.Volume{
{
Name: "bad volume",
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
},
},
},
}
failHostPathReadOnlyPSP := defaultPSP()
failHostPathReadOnlyPSP.Spec.Volumes = []policy.FSType{policy.HostPath}
failHostPathReadOnlyPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{
{
PathPrefix: "/foo",
ReadOnly: true,
},
}
failOtherSysctlsAllowedPSP := defaultPSP() failOtherSysctlsAllowedPSP := defaultPSP()
failOtherSysctlsAllowedPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "bar,abc" failOtherSysctlsAllowedPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "bar,abc"
...@@ -328,6 +354,11 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { ...@@ -328,6 +354,11 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
psp: failHostPathDirPSP, psp: failHostPathDirPSP,
expectedError: "is not allowed to be used", expectedError: "is not allowed to be used",
}, },
"failHostPathReadOnlyPSP": {
pod: failHostPathReadOnlyPod,
psp: failHostPathReadOnlyPSP,
expectedError: "must be read-only",
},
"failSafeSysctlFooPod with failNoSysctlAllowedSCC": { "failSafeSysctlFooPod with failNoSysctlAllowedSCC": {
pod: failSafeSysctlFooPod, pod: failSafeSysctlFooPod,
psp: failNoSysctlAllowedPSP, psp: failNoSysctlAllowedPSP,
...@@ -598,28 +629,82 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) { ...@@ -598,28 +629,82 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
Level: "level", Level: "level",
} }
hostPathDirPodVolumeMounts := []api.VolumeMount{
{
Name: "writeable /foo/bar",
ReadOnly: false,
},
{
Name: "read only /foo/bar/baz",
ReadOnly: true,
},
{
Name: "parent read only volume",
ReadOnly: true,
},
{
Name: "read only child volume",
ReadOnly: true,
},
}
hostPathDirPod := defaultPod() hostPathDirPod := defaultPod()
hostPathDirPod.Spec.InitContainers = []api.Container{
{
Name: defaultContainerName,
VolumeMounts: hostPathDirPodVolumeMounts,
},
}
hostPathDirPod.Spec.Containers[0].VolumeMounts = hostPathDirPodVolumeMounts
hostPathDirPod.Spec.Volumes = []api.Volume{ hostPathDirPod.Spec.Volumes = []api.Volume{
{ {
Name: "good volume", Name: "writeable /foo/bar",
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/foo/bar",
},
},
},
{
Name: "read only /foo/bar/baz",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{ HostPath: &api.HostPathVolumeSource{
Path: "/foo/bar/baz", Path: "/foo/bar/baz",
}, },
}, },
}, },
{
Name: "parent read only volume",
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/foo/",
},
},
},
{
Name: "read only child volume",
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/foo/readonly/child",
},
},
},
} }
hostPathDirPSP := defaultPSP() hostPathDirPSP := defaultPSP()
hostPathDirPSP.Spec.Volumes = []policy.FSType{policy.HostPath} hostPathDirPSP.Spec.Volumes = []policy.FSType{policy.HostPath}
hostPathDirPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{ hostPathDirPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{
{PathPrefix: "/foo/bar"}, // overlapping test case where child is different than parent directory.
{PathPrefix: "/foo/bar/baz", ReadOnly: true},
{PathPrefix: "/foo", ReadOnly: true},
{PathPrefix: "/foo/bar", ReadOnly: false},
} }
hostPathDirAsterisksPSP := defaultPSP() hostPathDirAsterisksPSP := defaultPSP()
hostPathDirAsterisksPSP.Spec.Volumes = []policy.FSType{policy.All} hostPathDirAsterisksPSP.Spec.Volumes = []policy.FSType{policy.All}
hostPathDirAsterisksPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{ hostPathDirAsterisksPSP.Spec.AllowedHostPaths = []policy.AllowedHostPath{
{PathPrefix: "/foo/bar"}, {PathPrefix: "/foo"},
} }
sysctlAllowFooPSP := defaultPSP() sysctlAllowFooPSP := defaultPSP()
......
...@@ -175,23 +175,27 @@ func GroupFallsInRange(id int64, rng policy.IDRange) bool { ...@@ -175,23 +175,27 @@ func GroupFallsInRange(id int64, rng policy.IDRange) bool {
// AllowsHostVolumePath is a utility for checking if a PSP allows the host volume path. // 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. // This only checks the path. You should still check to make sure the host volume fs type is allowed.
func AllowsHostVolumePath(psp *policy.PodSecurityPolicy, hostPath string) bool { func AllowsHostVolumePath(psp *policy.PodSecurityPolicy, hostPath string) (pathIsAllowed, mustBeReadOnly bool) {
if psp == nil { if psp == nil {
return false return false, false
} }
// If no allowed paths are specified then allow any path // If no allowed paths are specified then allow any path
if len(psp.Spec.AllowedHostPaths) == 0 { if len(psp.Spec.AllowedHostPaths) == 0 {
return true return true, false
} }
for _, allowedPath := range psp.Spec.AllowedHostPaths { for _, allowedPath := range psp.Spec.AllowedHostPaths {
if hasPathPrefix(hostPath, allowedPath.PathPrefix) { if hasPathPrefix(hostPath, allowedPath.PathPrefix) {
return true if !allowedPath.ReadOnly {
return true, allowedPath.ReadOnly
}
pathIsAllowed = true
mustBeReadOnly = true
} }
} }
return false return pathIsAllowed, mustBeReadOnly
} }
// hasPathPrefix returns true if the string matches pathPrefix exactly, or if is prefixed with pathPrefix at a path segment boundary // hasPathPrefix returns true if the string matches pathPrefix exactly, or if is prefixed with pathPrefix at a path segment boundary
......
...@@ -17,10 +17,11 @@ limitations under the License. ...@@ -17,10 +17,11 @@ limitations under the License.
package util package util
import ( import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"reflect" "reflect"
"testing" "testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
) )
// TestVolumeSourceFSTypeDrift ensures that for every known type of volume source (by the fields on // TestVolumeSourceFSTypeDrift ensures that for every known type of volume source (by the fields on
...@@ -105,41 +106,52 @@ func TestPSPAllowsFSType(t *testing.T) { ...@@ -105,41 +106,52 @@ func TestPSPAllowsFSType(t *testing.T) {
func TestAllowsHostVolumePath(t *testing.T) { func TestAllowsHostVolumePath(t *testing.T) {
tests := map[string]struct { tests := map[string]struct {
psp *policy.PodSecurityPolicy psp *policy.PodSecurityPolicy
path string path string
allows bool allows bool
mustBeReadOnly bool
}{ }{
"nil psp": { "nil psp": {
psp: nil, psp: nil,
path: "/test", path: "/test",
allows: false, allows: false,
mustBeReadOnly: false,
}, },
"empty allowed paths": { "empty allowed paths": {
psp: &policy.PodSecurityPolicy{}, psp: &policy.PodSecurityPolicy{},
path: "/test", path: "/test",
allows: true, allows: true,
mustBeReadOnly: false,
}, },
"non-matching": { "non-matching": {
psp: &policy.PodSecurityPolicy{ psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{ Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{ AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/foo"}, {
PathPrefix: "/foo",
ReadOnly: true,
},
}, },
}, },
}, },
path: "/foobar", path: "/foobar",
allows: false, allows: false,
mustBeReadOnly: false,
}, },
"match on direct match": { "match on direct match": {
psp: &policy.PodSecurityPolicy{ psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{ Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{ AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/foo"}, {
PathPrefix: "/foo",
ReadOnly: true,
},
}, },
}, },
}, },
path: "/foo", path: "/foo",
allows: true, allows: true,
mustBeReadOnly: true,
}, },
"match with trailing slash on host path": { "match with trailing slash on host path": {
psp: &policy.PodSecurityPolicy{ psp: &policy.PodSecurityPolicy{
...@@ -149,8 +161,9 @@ func TestAllowsHostVolumePath(t *testing.T) { ...@@ -149,8 +161,9 @@ func TestAllowsHostVolumePath(t *testing.T) {
}, },
}, },
}, },
path: "/foo/", path: "/foo/",
allows: true, allows: true,
mustBeReadOnly: false,
}, },
"match with trailing slash on allowed path": { "match with trailing slash on allowed path": {
psp: &policy.PodSecurityPolicy{ psp: &policy.PodSecurityPolicy{
...@@ -160,19 +173,24 @@ func TestAllowsHostVolumePath(t *testing.T) { ...@@ -160,19 +173,24 @@ func TestAllowsHostVolumePath(t *testing.T) {
}, },
}, },
}, },
path: "/foo", path: "/foo",
allows: true, allows: true,
mustBeReadOnly: false,
}, },
"match child directory": { "match child directory": {
psp: &policy.PodSecurityPolicy{ psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{ Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{ AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/foo/"}, {
PathPrefix: "/foo/",
ReadOnly: true,
},
}, },
}, },
}, },
path: "/foo/bar", path: "/foo/bar",
allows: true, allows: true,
mustBeReadOnly: true,
}, },
"non-matching parent directory": { "non-matching parent directory": {
psp: &policy.PodSecurityPolicy{ psp: &policy.PodSecurityPolicy{
...@@ -182,15 +200,19 @@ func TestAllowsHostVolumePath(t *testing.T) { ...@@ -182,15 +200,19 @@ func TestAllowsHostVolumePath(t *testing.T) {
}, },
}, },
}, },
path: "/foo", path: "/foo",
allows: false, allows: false,
mustBeReadOnly: false,
}, },
} }
for k, v := range tests { for k, v := range tests {
allows := AllowsHostVolumePath(v.psp, v.path) allows, mustBeReadOnly := AllowsHostVolumePath(v.psp, v.path)
if v.allows != allows { if v.allows != allows {
t.Errorf("%s expected %t but got %t", k, v.allows, allows) t.Errorf("allows: %s expected %t but got %t", k, v.allows, allows)
}
if v.mustBeReadOnly != mustBeReadOnly {
t.Errorf("mustBeReadOnly: %s expected %t but got %t", k, v.mustBeReadOnly, mustBeReadOnly)
} }
} }
} }
......
...@@ -51,6 +51,10 @@ message AllowedHostPath { ...@@ -51,6 +51,10 @@ message AllowedHostPath {
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar` // `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo` // `/foo` would not allow `/food` or `/etc/foo`
optional string pathPrefix = 1; optional string pathPrefix = 1;
// when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.
// +optional
optional bool readOnly = 2;
} }
message CustomMetricCurrentStatus { message CustomMetricCurrentStatus {
......
...@@ -960,6 +960,10 @@ type AllowedHostPath struct { ...@@ -960,6 +960,10 @@ type AllowedHostPath struct {
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar` // `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo` // `/foo` would not allow `/food` or `/etc/foo`
PathPrefix string `json:"pathPrefix,omitempty" protobuf:"bytes,1,rep,name=pathPrefix"` PathPrefix string `json:"pathPrefix,omitempty" protobuf:"bytes,1,rep,name=pathPrefix"`
// when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.
// +optional
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,2,opt,name=readOnly"`
} }
// FSType gives strong typing to different file systems that are used by volumes. // FSType gives strong typing to different file systems that are used by volumes.
......
...@@ -39,6 +39,7 @@ func (AllowedFlexVolume) SwaggerDoc() map[string]string { ...@@ -39,6 +39,7 @@ func (AllowedFlexVolume) SwaggerDoc() map[string]string {
var map_AllowedHostPath = map[string]string{ var map_AllowedHostPath = map[string]string{
"": "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. Deprecated: use AllowedHostPath from policy API Group instead.", "": "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. Deprecated: use AllowedHostPath from policy API Group instead.",
"pathPrefix": "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`", "pathPrefix": "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`",
"readOnly": "when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.",
} }
func (AllowedHostPath) SwaggerDoc() map[string]string { func (AllowedHostPath) SwaggerDoc() map[string]string {
......
...@@ -47,6 +47,10 @@ message AllowedHostPath { ...@@ -47,6 +47,10 @@ message AllowedHostPath {
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar` // `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo` // `/foo` would not allow `/food` or `/etc/foo`
optional string pathPrefix = 1; optional string pathPrefix = 1;
// when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.
// +optional
optional bool readOnly = 2;
} }
// Eviction evicts a pod from its node subject to certain policies and safety constraints. // Eviction evicts a pod from its node subject to certain policies and safety constraints.
......
...@@ -214,6 +214,10 @@ type AllowedHostPath struct { ...@@ -214,6 +214,10 @@ type AllowedHostPath struct {
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar` // `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo` // `/foo` would not allow `/food` or `/etc/foo`
PathPrefix string `json:"pathPrefix,omitempty" protobuf:"bytes,1,rep,name=pathPrefix"` PathPrefix string `json:"pathPrefix,omitempty" protobuf:"bytes,1,rep,name=pathPrefix"`
// when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.
// +optional
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,2,opt,name=readOnly"`
} }
// FSType gives strong typing to different file systems that are used by volumes. // FSType gives strong typing to different file systems that are used by volumes.
......
...@@ -39,6 +39,7 @@ func (AllowedFlexVolume) SwaggerDoc() map[string]string { ...@@ -39,6 +39,7 @@ func (AllowedFlexVolume) SwaggerDoc() map[string]string {
var map_AllowedHostPath = map[string]string{ var map_AllowedHostPath = map[string]string{
"": "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.", "": "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.",
"pathPrefix": "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`", "pathPrefix": "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`",
"readOnly": "when set to true, will allow host volumes matching the pathPrefix only if all volume mounts are readOnly.",
} }
func (AllowedHostPath) SwaggerDoc() map[string]string { func (AllowedHostPath) SwaggerDoc() map[string]string {
......
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