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

Extend the gc admission plugin to check ownerReference.blockOwnerDeletion #43876

Merged

Conversation

caesarxuchao
Copy link
Member

@caesarxuchao caesarxuchao commented Mar 30, 2017

#Extend the gc admission plugin to prevent user who doesn't have delete permission of the owner from changing blockOwnerDeletion field of existing ownerReferences, or adding ownerReference with blockOwnerDeletion=true.

The plugin need a RESTMapper to translate ownerRef.Kind to Resource. It should be using a dynamic one. However, as discussed in #42615, such a RESTMapper will be built after watchable discovery API is implemented, so in this PR the plugin is using the api.Registry.RESTMapper(), which is also used by the garbage collector currently.

Extending the gc admission plugin so that a user who doesn't have delete permission of the *owner* cannot modify blockOwnerDeletion field of existing ownerReferences, or add new ownerReference with blockOwnerDeletion=true

cc @lavalamp

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Mar 30, 2017
@k8s-github-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: caesarxuchao
We suggest the following additional approvers: @deads2k, @derekwaynecarr

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@k8s-github-robot k8s-github-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. release-note-label-needed labels Mar 30, 2017
@k8s-reviewable
Copy link

This change is Reviewable

@k8s-github-robot k8s-github-robot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Mar 30, 2017
@caesarxuchao
Copy link
Member Author

@k8s-bot verify test this

@deads2k
Copy link
Contributor

deads2k commented Mar 31, 2017

@sttts since I'm out next week

@caesarxuchao
Copy link
Member Author

@sttts could you take a look? Thanks.

}
allowed, reason, err := a.authorizer.Authorize(attribute)
if !allowed {
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion in an ownerReference refers to a resource you can't delete: %v, %v", reason, err))
Copy link
Contributor

Choose a reason for hiding this comment

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

s/in/if/


// Returns new blocking ownerReferences, and references whose blockOwnerDeletion field is changed.
// Changes between nil and false are ignored.
func changingBlockOwnerDeletion(newObj, oldObj runtime.Object) []metav1.OwnerReference {
Copy link
Contributor

Choose a reason for hiding this comment

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

Misleading name, sounds like a predicate. better: changedDeletionBlockingOwnerRefs.


var changedRefs []metav1.OwnerReference
indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences())
for _, newRef := range newRefs {
Copy link
Contributor

Choose a reason for hiding this comment

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

if an OwnerRef goes away in newObj, this will go unnoticed. Might be intended, but makes this unsymmetric. The func names all sound symmetric (changingBlockOwnerDeletion).

Copy link
Member Author

Choose a reason for hiding this comment

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

It's intentional. If a user has permission on the dependent object, she should have the right to remove the ownerRef so that the object doesn't share life cycle with the owner object.

Currently this admission plugin also disallows a user setting ref.blockOwnerDeletion from to true to false if she doesn't have delete permission of the owner object. Probably we should just allow that since the user can change the deletion behavior of the owner in the same way by removing the ref.

These two cases make the function asymmetric. I'll change the function name.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated the code to match what I wrote in the comment above.

return authorizer.AttributesRecord{
User: attributes.GetUserInfo(),
Verb: "delete",
// ownerReference can only refer to an object in the same namespace, so attributes.GetnNamespace() equals to the owner's namespace
Copy link
Contributor

Choose a reason for hiding this comment

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

GetnNamespace

for _, ref := range changedRefs {
attribute, err := a.toDeleteAttribute(ref, attributes)
if err != nil {
// An error occurs if a RESTMapping cannot be found for
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a security issue? If the RESTMapper is not up-to-date, we will allow ObjectReferences which would otherwise be forbidden. Until we have watchable discovery this might be fine as this only applies to TPRs. Can you add a comment that we switch to safe default "Forbidden" when we have watchable discovery ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. Further, if we are making the default "Forbidden" in the future, we should make it "Forbidden" now. Making validation rules tighter is backwards incompatible. Updated.

@caesarxuchao
Copy link
Member Author

Comments addresed. PTAL. Thanks.

return nil
}
newRefs := newMeta.GetOwnerReferences()
blockingNewRefs := blockingOwnerRefs(newRefs)
Copy link
Contributor

Choose a reason for hiding this comment

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

if this list is empty, return.

}

var ret []metav1.OwnerReference
indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences())
Copy link
Contributor

Choose a reason for hiding this comment

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

I would actually bet on it being just as fast to iterate the n-squared loop, but I don't really care.

var ret []metav1.OwnerReference
indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences())
for _, ref := range blockingNewRefs {
if oldRef, ok := indexedOldRefs[ref.UID]; ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

optional nit: reduce nesting depth like

oldRef, ok := indexOldRefs[ref.UID]
if !ok{
    ret = append(...
    continue
}
wasNotBlocking := ...
if wasNotBlocking{
    ret = append(...
    continue
}

func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) {
a.authorizer = authorizer
}

func (a *gcPermissionsEnforcement) SetRESTMapper(restMapper meta.RESTMapper) {
a.restMapper = restMapper
Copy link
Contributor

Choose a reason for hiding this comment

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

check that you have this in Validate

@@ -99,10 +120,93 @@ func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
return false
}

// translates ref to a DeleteAttribute deleting the object referred by the ref.
func (a *gcPermissionsEnforcement) toDeleteAttribute(ref metav1.OwnerReference, attributes admission.Attributes) (authorizer.AttributesRecord, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ownerRefToDeleteAttribute?

if err != nil {
return authorizer.AttributesRecord{}, err
}
mapping, err := a.restMapper.RESTMapping(schema.GroupKind{Group: groupVersion.Group, Kind: ref.Kind}, groupVersion.Version)
Copy link
Contributor

Choose a reason for hiding this comment

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

ResourcesFor and check for any match?

Copy link
Member Author

Choose a reason for hiding this comment

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

ResourcesFor takes a Resource, not a Kind, as input: https://github.com/kubernetes/apimachinery/blob/master/pkg/api/meta/interfaces.go#L136

RESTMapping().Resource seems the only way to translate Kind to Resource.

Copy link
Contributor

Choose a reason for hiding this comment

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

RESTMapping().Resource seems the only way to translate Kind to Resource.

RESTMappings then. We should handle the multi-match case.

Copy link
Member Author

Choose a reason for hiding this comment

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

The behavior is undefined if there are multiple matches. The admission controller cannot determine to which resource the user is trying to set the ownerReference.
In the garbage collector, it only handles the first match.
I think it's a bug in the design phase that the ownerReference records the Kind instead of Resource. I don't think we should fix it by trying all restmappings, rather, we should add a Resource field to the ownerReference. WDYT?

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 it's a bug in the design phase that the ownerReference records the Kind instead of Resource. I don't think we should fix it by trying all restmappings, rather, we should add a Resource field to the ownerReference. WDYT?

I have long thought that, but its controversial. In other places, @liggitt wants to keep kind assumes a canonical mapping from one to the other. I can't tell what @lavalamp wants given comments in the TPR thread, but I previously thought he liked the one to one mapping.

Given that there is more than one mapping possible, we can start out strict (must have delete access to all) and make it less strict later if we find it to be a problem.

I would not start down the path of changing the API without broad agreement.

Copy link
Member Author

Choose a reason for hiding this comment

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

Regardless of whether Kind <-> Resource is a one-on-one mapping, recording Resource in ownerReference seems correct, because Resource indicates a unique way to retrieve the owner object.

I think the problem of adding ownerReference.Resource now is that we have to keep it alongside the Kind field, due to the deprecation policy. That can cause confusion.

Given that there is more than one mapping possible, we can start out strict (must have delete access to all) and make it less strict later if we find it to be a problem.

Ok.

username: "non-rc-deleter",
resource: api.SchemeGroupVersion.WithResource("pods"),
newObj: podWithOwnerRefs(blockRC1, notBlockRC2),
expectedAllowed: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

We have multiple deny branches now. Please check the error message to make sure it's the failure you're expecting.

@deads2k
Copy link
Contributor

deads2k commented Apr 11, 2017

Looks structurally sound. A few comments I'd like to see fixed up.

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 12, 2017
@caesarxuchao caesarxuchao force-pushed the blockOwnerDeletion-admission branch from 5f2be3c to 587926c Compare April 12, 2017 22:29
@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 12, 2017
@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Apr 12, 2017
@caesarxuchao
Copy link
Member Author

@deads2k i addressed all the comments except the one regarding calling ResourcesFor(). I replied to that comment inline. PTAL. Thanks.

@caesarxuchao caesarxuchao force-pushed the blockOwnerDeletion-admission branch from 587926c to a956f7c Compare April 12, 2017 22:55
@deads2k
Copy link
Contributor

deads2k commented Apr 13, 2017

After the multiple mappings handled, this lgtm.

@caesarxuchao
Copy link
Member Author

Multiple-mapping case handled. PTAL. Thanks.

@deads2k
Copy link
Contributor

deads2k commented Apr 13, 2017

lgtm, don't forget to squash.

…rmission of the owner from setting blockOwnerDeletion
@caesarxuchao caesarxuchao force-pushed the blockOwnerDeletion-admission branch from fce1926 to 9d7a8df Compare April 13, 2017 18:55
@caesarxuchao
Copy link
Member Author

caesarxuchao commented Apr 13, 2017

Thanks for the reviews. Squashed. Applying the labels per comment.

@caesarxuchao caesarxuchao added lgtm "Looks good to me", indicates that a PR is ready to be merged. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Apr 13, 2017
@k8s-github-robot
Copy link

Automatic merge from submit-queue (batch tested with PRs 44440, 44038, 44302, 44316, 43876)

@k8s-github-robot k8s-github-robot merged commit 3b9eb1a into kubernetes:master Apr 14, 2017
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. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants