-
Notifications
You must be signed in to change notification settings - Fork 63
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 webhook checks for NamespacedRules #309
Add webhook checks for NamespacedRules #309
Conversation
@@ -188,7 +188,7 @@ func createGRRequest(t *testing.T, test testCase) *admission.Request { | |||
username = testUser | |||
} | |||
gvk := metav1.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "GlobalRole"} | |||
gvr := metav1.GroupVersionResource{Group: "management.cattle.io", Version: "v3", Resource: "globalrolebindings"} | |||
gvr := metav1.GroupVersionResource{Group: "management.cattle.io", Version: "v3", Resource: "globalroles"} |
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.
noticed a typo while looking in this file
907ceb1
to
efa4c8c
Compare
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.
From what I know and have seen this looks pretty good to me. I am still not too familiar with the role/rolebindings/globalrole/globalrolebindings objects but if the validators and mutators are all accounted for I approve. Definitely would have to have someone more familiar with the resources sign off as well.
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.
Overall comment:
- GRB Docs also need to be updated.
Nothing too big, just some various issues to consider.
pkg/resources/rbac.authorization.k8s.io/v1/role/validator_test.go
Outdated
Show resolved
Hide resolved
pkg/resources/rbac.authorization.k8s.io/v1/role/validator_test.go
Outdated
Show resolved
Hide resolved
pkg/resources/rbac.authorization.k8s.io/v1/rolebinding/validator.go
Outdated
Show resolved
Hide resolved
pkg/resources/rbac.authorization.k8s.io/v1/rolebinding/validator_test.go
Outdated
Show resolved
Hide resolved
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.
Seems good but I would like to see at least one unit tests handling multiple namespaces without the need for escalation (say nsA
with rules A
and nsB
with rules B
).
pkg/resources/management.cattle.io/v3/globalrolebinding/validator.go
Outdated
Show resolved
Hide resolved
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.
A couple of minor nits as well as some small issues:
- There are other criteria for invalid rules in addition to verbs. I'd recommend including them all.
- Sorry to go back on you, but I think that the check to see if the namespace exists might do more harm then good.
pkg/resources/management.cattle.io/v3/globalrolebinding/validator.go
Outdated
Show resolved
Hide resolved
pkg/resources/management.cattle.io/v3/globalrolebinding/validator_test.go
Outdated
Show resolved
Hide resolved
pkg/resources/rbac.authorization.k8s.io/v1/rolebinding/validator.go
Outdated
Show resolved
Hide resolved
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.
Couple of changes on the escalation checking logic, and some minor nits.
pkg/resources/common/common.go
Outdated
return false | ||
} | ||
|
||
// NewVerbChecker returns a function that checks if a user has the specified verb |
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 appreciate what you are trying to do with this function, but I think it's unnecessary. The auth
package provides an easily usable interface to check if a user has a given verb - I think this additional abstraction is adding complexity in an are where it can be dangerous (permission evaluation).
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 code becomes more complex if we want to
- Return all possible validation error instead of just the first one encountered
- Want to do an escalation check whenever we find an error
- Only do an escalation once
Caching whether we've done the escalation check and its result makes it simpler. But I realize that right now this checker:
- isn't really caching the result, but just defaulting to
false
if it's already been called - is named in a way that doesn't really describes it as a cached result
I think renaming the function (and changing its logic a bit) would make it clearer.
I think this additional abstraction is adding complexity in an are where it can be dangerous
I'd argue that no abstraction here was more complex. Do you think the proposed changes above would make it less complex? Or are you opposed in a caching behavior for this area of the code?
EDIT: Caching the result is ill-defined when an error can be returned with the SAR request. It's not clear if we should cache the returned error as well, or just return false if there was an error. Heh.
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 code becomes more complex if we want to
The first requirement is more fungible than the others, but sure, there is definitely some extra complexity introduced.
Caching whether we've done the escalation check and its result makes it simpler
The issue here is that this code doesn't effectively handle that.
Do you think the proposed changes above would make it less complex? Or are you opposed in a caching behavior for this area of the code?
I don't think that the proposed changes reduce complexity, particularly with the issues that it introduces. I'm not opposed to making sure that the escalate verb is checked for at most one time, but I don't think this abstraction solves that problem in a secure fashion.
I think that maintaining the state in the Admit function with two bools (and possibly using a side function to do the debouncing/caching) is a better option. However, I'm open to discussion.
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.
using a side function to do the debouncing/caching
Can you expand on this? Sounds like something similar to above but more "local" to the code that needs it.
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.
Sure, I'm thinking something like:
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
// some other code before
var hasEscalate, escalateChecked bool
err = auth.ConfirmNoEscalation(request, clusterRules, "", a.grbResolver)
if err != nil {
hasEscalate, escErr := a.hasEscalate(hasEscalate, escalateChecked, request, newGR.Name)
if err != nil {
return nil, escErr
}
escalateChecked = true
if hasEscalate {
return admission.ResponseAllowed(), nil
}
returnError = errors.Join(returnError, err)
}
// some other code after
}
func (a *admitter) hasEscalate(alreadyChecked bool, prevAnswer bool, request *admission.Request, grName string) (bool, error) {
if alreadyChecked {
return prevAnswer, nil
}
hasVerb, err := auth.RequestUserHasVerb(request, gvr, a.sar, escalateVerb, grName, "")
if err != nil {
return false, fmt.Errorf("unable to check if user has escalate: %w", err)
}
return hasVerb, nil
}
This has the following benefits over the current approach:
- The specific requirements of the GR/GRB validator (i.e. escalate is only checked once, but we only care about that one resource for any given request) are kept internal to that validator
- The state of what was called and when is kept inside the admit function, which is directing the flow and maintaining the state for this call (rather than in another call elsewhere
This is just my initial answer, if you see other ways to structure the side functions I'm also open to that.
for namespace, rules := range newGR.NamespacedRules { | ||
err = auth.ConfirmNoEscalation(request, rules, namespace, a.resolver) | ||
if err != nil { | ||
if hasEscalate(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.
Since GlobalRoles are cluster-scoped resources, you want to check if the user has "Escalate" at the cluster scope. Otherwise, users could take advantage of odd rbac configurations to give escalate in one namespace but not others, which we want to avoid.
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.
Could we add a test here to make the current implementation fail so that we don't introduce this mistake in the future?
pkg/resources/management.cattle.io/v3/globalrolebinding/validator.go
Outdated
Show resolved
Hide resolved
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.
@MbolotSuse 's got a couple of good comments. Nothing to add on top.
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.
Couple of comments on the new verb checker.
pkg/resources/common/common.go
Outdated
|
||
// cachedVerbChecker is used for caching if a request for a non-namespaced gvr with specified name has the given overrideVerb. | ||
// This is meant to eliminate the need to perform multiple calls to the provided SubjectAccessReview for the overrideVerb. | ||
type cachedVerbChecker struct { |
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.
A couple of overall comments on this struct:
- From what I've heard, typically in go you want to avoid constructors and allow objects to be constructed directly. @KevinJoiner might feel more strongly than I do on that point, and may have an opinion on if this is permissible in this case.
- It's a bit odd to have an unexported type, which doesn't implement an interface, returned from an exported function. I think your CI is failing because of this behavior (and an associated revive check) so you will probably need to adjust this
- I'd recommend emphasizing in your comment the limitations of this struct. Namely:
- Each admitter must request a new verbChecker for each request.
- Verb checkers must not be shared across different admitters.
- If the caller needs to change what it is checking (regardless of if it's a different verb, resource name, or resourceType), then they need to make a separate verb checker.
The reason I'm emphasizing these limitations is that this struct is used for access control, so a bug here is likely to turn into a CVE. We need to be very clear here to avoid issues.
pkg/resources/common/common.go
Outdated
return err | ||
} | ||
|
||
// HasVerb returns if the request have the overrideVerb. Defaults to false if it hasn't been checked. |
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.
Nit:
// HasVerb returns if the request have the overrideVerb. Defaults to false if it hasn't been checked. | |
// HasVerb returns if the request has the overrideVerb. Defaults to false if it hasn't been checked. |
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.
Couple of small changes on the new CachedVerbChecker.
21898c8
to
5e8472e
Compare
…lation checks for GlobalRoles
2a0981e
to
b40579c
Compare
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.
One comment on an unhandled error. Outside of that looks good.
NOTE: Currently uses my local rancher changes because it requires work currently in a PR
Issue: rancher/rancher#42216
Problem
Follow up work from: rancher/rancher#43018
There are 2 keys changes. First we add escalation checks for the new NamespacedRules field of GlobalRoles. Second we add a check to prevent modification/removal of the gr-owner and grb-owner labels.
Solution
Added two new validation webhooks that select based on the gr-owner and grb-owner labels.
Added a check in the the GlobalRole validation to ensure there is no escalation.
CheckList