Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for enforcing read only host paths in PSPs. #58647

Conversation

jhorwit2
Copy link
Contributor

@jhorwit2 jhorwit2 commented Jan 23, 2018

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 kubernetes/enhancements#5

Special notes for your reviewer:

Release note:

PodSecurityPolicy now supports restricting hostPath volume mounts to be readOnly and under specific path prefixes

/cc @ericchiang @liggitt

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jan 23, 2018
@k8s-github-robot k8s-github-robot added the kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API label Jan 23, 2018
@jhorwit2 jhorwit2 force-pushed the for/upstream/master/hostpath-psp-readonly branch from 7aecff0 to d4acf52 Compare January 23, 2018 02:24
@@ -878,6 +878,14 @@ type AllowedHostPath struct {
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo`
PathPrefix string

// ReadOnly when set to true will force volumes matching the path prefix to run as read-only.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will force volumes matching the path prefix to run as read-only

I don't think this should modify pod specs, which is what that sounds like.

Could be better phrased as "when set to true, will allow host volumes matching the pathPrefix if all volume mounts are readOnly", since you could have the following:

allowedHostPaths:[
{pathPrefix:"/foo", readOnly:true},
{pathPrefix:"/foo/bar", readOnly:false}
]

which would allow a writeable /foo/bar mount

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch. I copied the comment style from something does does modify the spec.

}

if allowedHostPath.ReadOnly {
for cIdx, c := range pod.Spec.InitContainers {
Copy link
Member

@liggitt liggitt Jan 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't quite right. see the example at #58647 (comment)

make sure there's a testcase for the overlapping-allowedHostPaths-with-different-readonly-settings scenario

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That link isn't going any where specific for me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird, was just a link to the comment above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still outstanding

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind looking at the tests I added on the 10th? Is it still outstanding? I thought I covered this there.

@jhorwit2 jhorwit2 force-pushed the for/upstream/master/hostpath-psp-readonly branch from d4acf52 to d09eff9 Compare February 11, 2018 02:54
@jhorwit2 jhorwit2 changed the title WIP Add support for enforcing read only host paths in PSPs. Add support for enforcing read only host paths in PSPs. Feb 11, 2018
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 11, 2018
@jhorwit2
Copy link
Contributor Author

@liggitt PTAL. I fixed the overlapping issue and added some test cases to cover it.

@@ -878,6 +878,14 @@ type AllowedHostPath struct {
// `/foo` would allow `/foo`, `/foo/` and `/foo/bar`
// `/foo` would not allow `/food` or `/etc/foo`
PathPrefix string

// ReadOnly when set to true, will allow host volumes matching the pathPrefix if all volume mounts are readOnly.
// If the container specifically requests to run with a non-read only volume that matches the path prefix
Copy link
Member

@liggitt liggitt Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the container specifically requests to run with a non-read only volume that matches the path prefix then the PSP should deny the pod

this isn't accurate... another allowsHostVolume could allow it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably just say when set to true, will allow host volumes matching the pathPrefix if all volume mounts are readOnly

if len(s.psp.Spec.AllowedHostPaths) == 0 {
// If no allowed paths are specified then allow any path
// since host path volumes are allowed.
continue
Copy link
Member

@liggitt liggitt Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continueing in "success" cases assumes we'll never add other things that need checking later on in the loop. keep this structured to continue only to short-circuit the current volume checking in case of an error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liggitt this is keeping the same logic that we had in the util method before. I was trying to decrease the number of changes required to make this change. You'd prefer i set a variable then below check it before emitting every error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Liggitt is saying is that you're now continueing over stuff like flex volume validation further on. I'm personally confused why len(s.psp.Spec.AllowedHostPaths) == 0 would cause you to skip the flex volume checks.

Maybe just change the above if statement to:

if fsType == extensions.HostPath && len(s.psp.Spec.AllowedHostPaths) > 1 {
    // ...
}

Copy link
Contributor Author

@jhorwit2 jhorwit2 Apr 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericchiang It would never skip flex volume checks if the fs type was flex volume because a volume can't be two types at the same time. So if it reached the existing allowed host path check then it must be a host volume and nothing else.

if fsType == extensions.HostPath {
	if len(s.psp.Spec.AllowedHostPaths) == 0 {
		// If no allowed paths are specified then allow any path
		// since host path volumes are allowed.
		continue
	}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd prefer i set a variable then below check it before emitting every error?

No, I'd prefer to only continue early in cases where we added an error. The current change adds continue statements in non-error cases.

It would never skip flex volume checks if the fs type was flex volume because a volume can't be two types at the same time. So if it reached the existing allowed host path check then it must be a host volume and nothing else.

Continuing early in non-error cases assumes all other checks will be specific to other volume types, which is an assumption I'm trying to avoid adding here.

I was trying to decrease the number of changes required to make this change.

I do agree on minimal change... I think it can be contained entirely to this block and the AllowsHostVolumePath helper:

--- a/pkg/security/podsecuritypolicy/provider.go
+++ b/pkg/security/podsecuritypolicy/provider.go
@@ -229,10 +229,27 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
                        }

                        if fsType == extensions.HostPath {
-                               if !psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path) {
+                               pathIsAllowed, mustBeReadOnly := psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path)
+                               if !pathIsAllowed {
                                        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")))
+                               } else if mustBeReadOnly {
+                                       // Ensure all the VolumeMounts that use this volume are readonly
+                                       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"))
+                                                       }
+                                               }
+                                       }
                                }
                        }
--- a/pkg/security/podsecuritypolicy/util/util.go
+++ b/pkg/security/podsecuritypolicy/util/util.go
@@ -173,25 +173,29 @@ 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 {
+// AllowsHostVolumePath is a utility for checking if a PSP allows the host volume path, and whether volume mounts
+// that use the path are required to be readonly. 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) (pathIsAllowed, mustBeReadOnly bool) {
        if psp == nil {
-               return false
+               return false, false
        }

        // If no allowed paths are specified then allow any path
        if len(psp.Spec.AllowedHostPaths) == 0 {
-               return true
+               return true, false
        }

        for _, allowedPath := range psp.Spec.AllowedHostPaths {
                if hasPathPrefix(hostPath, allowedPath.PathPrefix) {
-                       return true
+                       if !allowedPath.ReadOnly {
+                               return true, false
+                       }
+                       pathIsAllowed = true
+                       mustBeReadOnly = true
                }
        }

-       return false
+       return pathIsAllowed, mustBeReadOnly
 }

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 23, 2018
@jhorwit2 jhorwit2 force-pushed the for/upstream/master/hostpath-psp-readonly branch from d09eff9 to 91beaa8 Compare May 22, 2018 15:32
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels May 22, 2018
@jhorwit2 jhorwit2 force-pushed the for/upstream/master/hostpath-psp-readonly branch 3 times, most recently from 9972e5e to 226fd5f Compare May 22, 2018 18:21
@jhorwit2
Copy link
Contributor Author

/assign @liggitt

@@ -773,3 +773,277 @@ type ReplicaSetCondition struct {
// +optional
Message string
}

// +genclient
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop the changes to this file

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this got added in this commit, then removed in the next. please squash or fixup to avoid noise

@liggitt
Copy link
Member

liggitt commented May 30, 2018

/kind feature

@k8s-ci-robot k8s-ci-robot added the kind/feature Categorizes issue or PR as related to a new feature. label May 30, 2018
@liggitt
Copy link
Member

liggitt commented May 30, 2018

/priority important-soon

@k8s-ci-robot k8s-ci-robot added the priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. label May 30, 2018
@liggitt
Copy link
Member

liggitt commented May 30, 2018

oh, I still think we should put language in the PSP doc (either on the pathPrefix or readOnly field) that very explicitly warns about things that would let a pod circumvent the pathPrefix control:

  • The PSP must set privileged to false, and set allowedHostPaths[*].readOnly to true, or the container can write to the filesystem in ways that let it traverse the host filesystem outside the pathPrefix

@tallclair tallclair self-assigned this May 31, 2018
@k8s-github-robot
Copy link

[MILESTONENOTIFIER] Milestone Pull Request: Up-to-date for process

@jhorwit2 @liggitt @tallclair

Pull Request Labels
  • sig/auth sig/storage: Pull Request will be escalated to these SIGs if needed.
  • priority/important-soon: Escalate to the pull request owners and SIG owner; move out of milestone after several unsuccessful escalation attempts.
  • kind/feature: New functionality.
Help

@tallclair
Copy link
Member

tallclair commented May 31, 2018

I somehow missed this PR until today, so I apologize in advance for the late feedback.

AllowedPaths isn't really useful unless ALL paths are marked readonly (as @liggitt referred to above). Also, it might be useful to have this option for other volume types as well. Given that, what do you think of this alternative API:

// ReadOnlyVolumes is a list of volume types that must be mounted read-only.
ReadOnlyVolumes []FSType

This could either be a separate whitelist from Volumes (i.e. a volume type should only be in one list, not both), or a strict subset of Volumes (i.e. put it in Volumes to allow it, and ReadOnlyVolumes to force read only).

WDYT?

@liggitt
Copy link
Member

liggitt commented May 31, 2018

I'm not sure about pinning readonly restrictions to all volumes of a specific type. Are the reasons that require them all to be readonly mostly unique to hostPath?

If we anticipate being able to limit hostPath volumes to particular types (like socket) and pinned paths (not just prefixes) in the future, the per-hostPath construct makes more sense

@liggitt
Copy link
Member

liggitt commented May 31, 2018

/retest

@msau42
Copy link
Member

msau42 commented May 31, 2018

lgtm from storage perspective. I agree with @liggitt that requiring volumes to be read only for other volume types may not be as useful. Especially with PVCs being namespaced.

@dims
Copy link
Member

dims commented Jun 1, 2018

/test pull-kubernetes-e2e-kops-aws

@liggitt
Copy link
Member

liggitt commented Jun 2, 2018

/retest

@liggitt
Copy link
Member

liggitt commented Jun 2, 2018

lgtm from storage perspective. I agree with @liggitt that requiring volumes to be read only for other volume types may not be as useful. Especially with PVCs being namespaced.

@tallclair WDYT?

@jhorwit2 jhorwit2 force-pushed the for/upstream/master/hostpath-psp-readonly branch from 89cf9d8 to 607b236 Compare June 4, 2018 16:16
@tallclair
Copy link
Member

Ok, I'm convinced. This LGTM.

@liggitt
Copy link
Member

liggitt commented Jun 4, 2018

docs in kubernetes/website#8898

@jhorwit2 jhorwit2 force-pushed the for/upstream/master/hostpath-psp-readonly branch from 607b236 to c7fbcf3 Compare June 4, 2018 23:11
@liggitt
Copy link
Member

liggitt commented Jun 4, 2018

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 4, 2018
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jhorwit2, liggitt

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 4, 2018
@liggitt
Copy link
Member

liggitt commented Jun 5, 2018

/retest

@k8s-github-robot
Copy link

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 here.

@k8s-github-robot k8s-github-robot merged commit f731010 into kubernetes:master Jun 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/storage Categorizes an issue or PR as relevant to SIG Storage. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ReadOnly/Writable AllowedHostPath for PodSecurityPolicy
9 participants