-
Notifications
You must be signed in to change notification settings - Fork 40.3k
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
Extend the gc admission plugin to check ownerReference.blockOwnerDeletion #43876
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: caesarxuchao
Needs 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)) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
Comments addresed. PTAL. Thanks. |
return nil | ||
} | ||
newRefs := newMeta.GetOwnerReferences() | ||
blockingNewRefs := blockingOwnerRefs(newRefs) |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
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) |
#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