Repository navigation
Extend the gc admission plugin to check ownerReference.blockOwnerDeletion - #43876
k8s-github-robot merged 1 commit into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: caesarxuchao DetailsNeeds approval from an approver in each of these OWNERS Files:
You can indicate your approval by writing |
|
@k8s-bot verify test this |
|
@sttts since I'm out next week |
|
@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)) |
|
|
||
| // 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 { |
There was a problem hiding this comment.
Misleading name, sounds like a predicate. better: changedDeletionBlockingOwnerRefs.
|
|
||
| var changedRefs []metav1.OwnerReference | ||
| indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences()) | ||
| for _, newRef := range newRefs { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
| for _, ref := range changedRefs { | ||
| attribute, err := a.toDeleteAttribute(ref, attributes) | ||
| if err != nil { | ||
| // An error occurs if a RESTMapping cannot be found for |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
|
Comments addresed. PTAL. Thanks. |
| return nil | ||
| } | ||
| newRefs := newMeta.GetOwnerReferences() | ||
| blockingNewRefs := blockingOwnerRefs(newRefs) |
There was a problem hiding this comment.
if this list is empty, return.
| } | ||
|
|
||
| var ret []metav1.OwnerReference | ||
| indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences()) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) SetRESTMapper(restMapper meta.RESTMapper) { | ||
| a.restMapper = restMapper |
There was a problem hiding this comment.
check that you have this in Validate
| } | ||
|
|
||
| // 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) { |
There was a problem hiding this comment.
ownerRefToDeleteAttribute?
| if err != nil { | ||
| return authorizer.AttributesRecord{}, err | ||
| } | ||
| mapping, err := a.restMapper.RESTMapping(schema.GroupKind{Group: groupVersion.Group, Kind: ref.Kind}, groupVersion.Version) |
There was a problem hiding this comment.
ResourcesFor and check for any match?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
RESTMapping().Resource seems the only way to translate Kind to Resource.
RESTMappings then. We should handle the multi-match case.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
We have multiple deny branches now. Please check the error message to make sure it's the failure you're expecting.
|
Looks structurally sound. A few comments I'd like to see fixed up. |
5f2be3c to
587926c
Compare
|
@deads2k i addressed all the comments except the one regarding calling |
587926c to
a956f7c
Compare
|
After the multiple mappings handled, this lgtm. |
|
Multiple-mapping case handled. PTAL. Thanks. |
|
lgtm, don't forget to squash. |
…rmission of the owner from setting blockOwnerDeletion
fce1926 to
9d7a8df
Compare
|
Thanks for the reviews. Squashed. Applying the labels per comment. |
|
Automatic merge from submit-queue (batch tested with PRs 44440, 44038, 44302, 44316, 43876) |
…ion-admission Automatic merge from submit-queue (batch tested with PRs 44440, 44038, 44302, 44316, 43876) Extend the gc admission plugin to check ownerReference.blockOwnerDeletion #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 kubernetes#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](https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-controller-manager/app/core.go#L165-L166) by the garbage collector currently. ```release-note 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
#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.cc @lavalamp