-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
🌱 adding TokenReview.auth.k8s.io/v1 webhook support #1440
🌱 adding TokenReview.auth.k8s.io/v1 webhook support #1440
Conversation
@rfranzke would you be interested in giving this a review and seeing if it aligns with what you implemented for Gardener? |
/assign @alvaroaleman |
4d07c03
to
a9ebca3
Compare
/test pull-controller-runtime-test-master |
/assign |
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.
Thanks @christopherhein, I don't have too many comments because I'm not very familiar with the design decisions made for the admission webhooks, but overall I like it and it looks like it's going into the right direction! :)
Once this PR is merged, I will use it as reference to also contribute the authorization handling.
Generally, I see that many things are now "duplicated" and this might then happen again for the authz. Maybe we should try more to encapsulate shared functionality to make it reusable (not sure if this is always possible in all cases). Probably a maintainer can give better recommendations here.
pkg/webhook/authentication/http.go
Outdated
var body []byte | ||
var err error | ||
ctx := r.Context() | ||
if wh.WithContextFunc != nil { | ||
ctx = wh.WithContextFunc(ctx, r) | ||
} | ||
|
||
var reviewResponse Response | ||
if r.Body != nil { | ||
if body, err = ioutil.ReadAll(r.Body); err != nil { | ||
wh.log.Error(err, "unable to read the body from the incoming request") | ||
reviewResponse = Errored(err) | ||
wh.writeResponse(w, reviewResponse) | ||
return | ||
} | ||
} else { | ||
err = errors.New("request body is empty") | ||
reviewResponse = Errored(err) | ||
wh.writeResponse(w, reviewResponse) | ||
return | ||
} | ||
|
||
// verify the content type is accurate | ||
contentType := r.Header.Get("Content-Type") | ||
if contentType != "application/json" { | ||
err = fmt.Errorf("contentType=%s, expected application/json", contentType) | ||
wh.log.Error(err, "unable to process a request with an unknown content type", "content type", contentType) | ||
reviewResponse = Errored(err) | ||
wh.writeResponse(w, reviewResponse) | ||
return | ||
} |
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.
This is exactly the same like in
controller-runtime/pkg/webhook/admission/http.go
Lines 46 to 77 in b5065bd
var body []byte | |
var err error | |
ctx := r.Context() | |
if wh.WithContextFunc != nil { | |
ctx = wh.WithContextFunc(ctx, r) | |
} | |
var reviewResponse Response | |
if r.Body != nil { | |
if body, err = ioutil.ReadAll(r.Body); err != nil { | |
wh.log.Error(err, "unable to read the body from the incoming request") | |
reviewResponse = Errored(http.StatusBadRequest, err) | |
wh.writeResponse(w, reviewResponse) | |
return | |
} | |
} else { | |
err = errors.New("request body is empty") | |
wh.log.Error(err, "bad request") | |
reviewResponse = Errored(http.StatusBadRequest, err) | |
wh.writeResponse(w, reviewResponse) | |
return | |
} | |
// verify the content type is accurate | |
contentType := r.Header.Get("Content-Type") | |
if contentType != "application/json" { | |
err = fmt.Errorf("contentType=%s, expected application/json", contentType) | |
wh.log.Error(err, "unable to process a request with an unknown content type", "content type", contentType) | |
reviewResponse = Errored(http.StatusBadRequest, err) | |
wh.writeResponse(w, reviewResponse) | |
return | |
} |
Probably it should be made re-useable and moved into a function?
Rethinking this implementation a bit I was able to cut down on a bunch of the duplication, @rfranzke about the checks, got ideas on how we could pass all the different implementations of |
057ee0d
to
fe774d6
Compare
@alvaroaleman any chance you'd have some time to review this? 🙏 |
// unregistered type to the v1 GVK, the decoder will coerce a v1beta1 TokenReview to v1. | ||
// The actual TokenReview GVK will be used to write a typed response in case the | ||
// webhook config permits multiple versions, otherwise this response will fail. | ||
req := Request{} |
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.
this is maybe more dangerous that it might seem? What if a new field is added to v1beta1 that's actually different than v1 (they're not guarnateed to be the same, just round-trippable). We could end up making a decision on bad data, right?
I suppose for new named fields this is the same as operating on old types against a new apiserver.
If v1
and v1beta1
both introduced a field called foo
with different types though, this would just break entirely.
Is v1beta1
deprecated? May be worth commenting, cause that makes the chances of that happening much less.
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.
v1beta1
is deprecated in 1.19 as per - https://github.com/kubernetes/api/blob/master/authentication/v1beta1/types.go#L25-L31 currently it looks like v1
and v1beta1
are in parity. Given it's deprecated I assume that means we can support this with the expectation that v1beta1 shouldn't chnage, right?
If so I can add a note to the comment above. A lot of this was carried over from the Admissions webhook, even this comment tbh.
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.
For now I've added a comment, mentioning that v1beta1
is deprecated as of 1.19
and will be removed in 1.22
per the v1.22 deprecation guide - https://kubernetes.io/docs/reference/using-api/deprecation-guide/#tokenreview-v122
a0dcd8d
to
a0e1d83
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.
Sorry for the delay in reviewing, I have a lot on my plate right now and this is a pretty big PR :) I am also not very familiar with the existing webhook code, so apologies if I am criticizing something you got from there.
pkg/webhook/authentication/http.go
Outdated
|
||
var reviewResponse Response | ||
if r.Body != nil { | ||
if body, err = ioutil.ReadAll(r.Body); err != nil { |
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 need to close the body if its non-nil
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.
return | ||
} | ||
} else { | ||
err = errors.New("request body is empty") |
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 the godocs of net/http.Request:
For server requests, the Request Body is always non-nil
// but will return EOF immediately when no body is present.
So this doesn't need to be an else
but simply at the top level after the r.Body != nil
and check for the length of var body
instead
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.
This is another carryover from the admission package. https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/webhook/admission/http.go#L53-L67
That being said since we're not actually checking the HTTP Method, I believe this is what this block is meant to confirm that this isn't a GET, but I could have also understood the goals of 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.
Do you think I should remove this?
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've switched around the way this is handled, the first check r.Body == nil
now checks if it's a request that doesn't support a body, like a Get
and fails out then we go on to reading the body.
} | ||
|
||
// verify the content type is accurate | ||
contentType := r.Header.Get("Content-Type") |
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 check needed? Deserialization will fail if its not json or not?
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.
This was carried over from the admission package. https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/webhook/admission/http.go#L69-L77 I'd guess this is to give a bit more visibility into what actually went wrong in the request. Should I remove 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.
So I guess that isn't what happens, the decoder doesn't seem to check what the actual headers are. If you don't have this check and the body is valid the request could be successful or could fail other places.
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.
you get better errors this way too -- if you try to deserialize proto as json, for instance, you just end up with a really confusing error, whereas if you explicitly check content-type
, you can provide a better error (plus, it's more correct, and like maybe theoretically avoids weird behavior where some blob of non-json looks enough like json to be deserialized, but is subtly wrong).
a0e1d83
to
3c8b36e
Compare
3c8b36e
to
53d0278
Compare
/test pull-controller-runtime-test-master |
53d0278
to
9e86edf
Compare
Signed-off-by: Chris Hein <[email protected]>
9e86edf
to
6ae3ee1
Compare
@alvaroaleman this should be all updated. |
)) | ||
|
||
By("checking that a message is populated for 'Unauthenticated' responses") | ||
Expect(ReviewResponse(false, authenticationv1.UserInfo{}, "UNACCEPTABLE!")).To(Equal( |
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.
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.
so unacceptable 😝
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.
/lgtm
/approve
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: christopherhein, DirectXMan12 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 |
/test pull-controller-runtime-test-master |
This adds a new
pkg/webhook/authentication/
package for setting up theServeHTTP
func for the webook server to be able to handleTokenReview.authentication.k8s.io/(v1,v1beta1)
requests.Closes #1436
Signed-off-by: Chris Hein [email protected]