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

Add verification to apiserver redirect following #66516

Merged
merged 1 commit into from Sep 26, 2018

Conversation

tallclair
Copy link
Member

@tallclair tallclair commented Jul 23, 2018

What this PR does / why we need it:
Only allow the apiserver to follow redirects to the same host.

Release note:

Restrict redirect following from the apiserver to same-host redirects, and ignore redirects in some cases.

@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jul 23, 2018
@tallclair tallclair added the kind/bug Categorizes issue or PR as related to a bug. label Jul 23, 2018
@tallclair
Copy link
Member Author

tallclair commented Jul 23, 2018

/sig api-machinery

@k8s-ci-robot k8s-ci-robot added the sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. label Jul 23, 2018
@@ -343,7 +343,7 @@ func ConnectWithRedirects(originalMethod string, originalLocation *url.URL, head

redirectLoop:
for redirects := 0; ; redirects++ {
if redirects > maxRedirects {
if redirects >= maxRedirects {
Copy link
Member

Choose a reason for hiding this comment

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

first request isn't a redirect, so I think > is correct... maxRedirects=1 would let you go through the loop twice

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 should fail on the 10th redirect, not allow 10 redirects, at least to be consistent with http.Client. I changed this because the test caught this inconsistency.

Copy link
Member

Choose a reason for hiding this comment

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

I would expect maxRedirects=10 to allow 10 and fail on 11. I don't think it really matters in practice either way as long as we don't set maxRedirects to 1

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like to keep this consistent with the default http redirect checker, so I reverted the logic but changed maxRedirects to 9. (Also fixed the spdy test with the new max)

@@ -404,6 +404,11 @@ redirectLoop:
if err != nil {
return nil, nil, fmt.Errorf("malformed Location header: %v", err)
}

// Only allow redirects to the same host.
if location.Host != originalLocation.Host {
Copy link
Member

Choose a reason for hiding this comment

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

compare scheme as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

I considered that, but I think at least http -> https should be allowed. And if an attacker controls the endpoint enough to send an https redirect response, then they don't gain anything from a downgrade attack.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense

}
// Only allow redirects to the same host.
newHost := req.URL.Host
originalHost := via[0].URL.Host
Copy link
Member

Choose a reason for hiding this comment

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

is len(via) always greater than 0?

Copy link
Member Author

Choose a reason for hiding this comment

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

@tallclair
Copy link
Member Author

@mrunalp - For CRI-o
@Random-Liu - For containerd

This will break setups where the apiserver sends the initial request on one IP address to the node, and the CRI streaming response sends a redirect to a different interface (e.g. internal vs. external address). This isn't an issue with dockershim, because it always uses a relative redirect. Unfortunately that doesn't appear to be the case with CRI-o (not sure about contianerd). How is CRI-o and contianerd streaming typically configured?

@tallclair
Copy link
Member Author

@kubernetes/sig-node-pr-reviews

@k8s-ci-robot k8s-ci-robot added the sig/node Categorizes an issue or PR as relevant to SIG Node. label Jul 23, 2018
@liggitt
Copy link
Member

liggitt commented Jul 23, 2018

This will break setups where the apiserver sends the initial request on one IP address to the node, and the CRI streaming response sends a redirect to a different interface (e.g. internal vs. external address)

Or external DNS name vs external IP, etc

@tallclair
Copy link
Member Author

OK, updated this PR with a few changes:

  1. Allow same-host-different-port redirects. This is required for out-of-process CRIs, which need to listen on a different port than the Kubelet.
  2. Blanket disable redirects in the LocationStreamer. This streamer is only used by the log pod subresource, which is served by the Kubelet and should never redirect.
  3. Add a feature gate to enable same-host redirect validation, defaulted to disabled. I think it's too risky to push this by default, at least for past releases, for the reasons discussed above (CRI could respond with a different hostname / interface). I think most clusters should be able to safely enable this, so we can consider making it the default for 1.12.

@liggitt PTAL

//
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
StreamingProxyRedirects utilfeature.Feature = "StreamingProxyRedirects"

// owner: @tallclair
// alpha: v1.9
Copy link
Member Author

Choose a reason for hiding this comment

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

BTW - I put 1.9 here because I think we should cherrypick this back to 1.9

Copy link
Member

Choose a reason for hiding this comment

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

Still? :)

Copy link
Member Author

Choose a reason for hiding this comment

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

🙄

@philips
Copy link
Contributor

philips commented Jul 25, 2018

/lgtm

I would suggest a comment on why log never can redirect around here: https://github.com/kubernetes/kubernetes/pull/66516/files#diff-02939fae98eaa89403de3ea191772176R89

@k8s-ci-robot k8s-ci-robot added 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. and removed lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesn't merit a release note. labels Jul 25, 2018
@tallclair
Copy link
Member Author

  • Add comment to LocationStreamer about redirect change
  • Change LocationStreamer to forward the redirect back to the client, rather than outright failing
  • Added release note.

@fedebongio
Copy link
Contributor

/cc @roycaihw

@philips
Copy link
Contributor

philips commented Jul 30, 2018

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jul 30, 2018
Location: loc,
}
_, _, _, err = streamer.InputStream("", "")
assert.Equal(t, http.ErrUseLastResponse, err, "Expected error from redirect")
Copy link
Member

Choose a reason for hiding this comment

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

If CheckRedirect returns ErrUseLastResponse, then the most recent response is returned with its body unclosed, along with a nil error: https://github.com/golang/go/blob/214f7ec554888042a7a54fdca9cba19aee8ebaf1/src/net/http/client.go#L583-L588

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, thanks. On closer inspection, forwarding the redirect doesn't make sense here since only the request body is returned. I reverted the change back to outright rejecting redirects.

@tallclair
Copy link
Member Author

Finally got back around to this. PTAL.

@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Sep 21, 2018
@cjcullen
Copy link
Member

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 21, 2018
@k8s-ci-robot k8s-ci-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 21, 2018
@tallclair
Copy link
Member Author

Fixed breakage caused by rebase & squashed commits.

@philips
Copy link
Contributor

philips commented Sep 22, 2018

/lgtm

(very brief scroll through because of amount of review so far)

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 22, 2018
staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go Outdated Show resolved Hide resolved

// Only follow redirects to the same host. Otherwise, propogate the redirect response back.
if validateRedirects && location.Hostname() != originalLocation.Hostname() {
break redirectLoop
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't you be returning an error?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, this returns the redirect response back to the client. See the discussion on this comment thread: #66516 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Do you think code calling this function is prepared to get a redirection response?

Copy link
Member

Choose a reason for hiding this comment

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

(not to argue with @liggitt's comment-like in that thread, but... given what you told me in person I think it's probably right to go ahead and return the error, and just explicitly say that hostname to IP redirections (or vice versa) are not supported even if they happen to refer to the same thing.)

Copy link
Member Author

Choose a reason for hiding this comment

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

If the CRI returned a different internal host address, then returning that to the client would be meaningless, and an error response would make more sense. However, if the pod returns a redirect to an external service (trivial example: a pod that just returns a redirect response to a different external web address), then the expected behavior would be that the client can follow that redirect.

Do you think code calling this function is prepared to get a redirection response?

Looking at all the call sites, yes - it shouldn't be a problem. Also, this method only handles 302 redirects, so all other redirects were already being returned to the client.

Copy link
Member

Choose a reason for hiding this comment

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

OK, sounds great :)

@k8s-ci-robot k8s-ci-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 25, 2018
Copy link
Member Author

@tallclair tallclair left a comment

Choose a reason for hiding this comment

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

Thanks @lavalamp. PTAL

staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go Outdated Show resolved Hide resolved

// Only follow redirects to the same host. Otherwise, propogate the redirect response back.
if validateRedirects && location.Hostname() != originalLocation.Hostname() {
break redirectLoop
Copy link
Member Author

Choose a reason for hiding this comment

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

No, this returns the redirect response back to the client. See the discussion on this comment thread: #66516 (comment)

@lavalamp
Copy link
Member

please squash, thanks!

@tallclair
Copy link
Member Author

Squashed. Thanks!

@@ -28,6 +28,8 @@ import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Copy link
Member

Choose a reason for hiding this comment

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

not going to ask for another change at this point but technically 3rd party deps get their own section...

@lavalamp
Copy link
Member

/approve
/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 26, 2018
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cjcullen, lavalamp, philips, rphillips, tallclair

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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 26, 2018
@fejta-bot
Copy link

/retest
This bot automatically retries jobs that failed/flaked on approved PRs (send feedback to fejta).

Review the full test history for this PR.

Silence the bot with an /lgtm cancel comment for consistent failures.

@k8s-ci-robot k8s-ci-robot merged commit 109b67c into kubernetes:master Sep 26, 2018
k8s-ci-robot added a commit that referenced this pull request Oct 22, 2018
…516-upstream-release-1.12-1539730092

Automated cherry-pick of #66516 to v1.12
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. area/apiserver area/kubelet cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/bug Categorizes issue or PR as related to a bug. 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. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. sig/node Categorizes an issue or PR as relevant to SIG Node. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet