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

Disable HTTP2 while proxying a "Connection: upgrade" request #88781

Merged
merged 1 commit into from Mar 6, 2020

Conversation

ibuildthecloud
Copy link
Contributor

@ibuildthecloud ibuildthecloud commented Mar 3, 2020

When proxying connection upgrade requests, like websockets, we dial
the target and then manually write the http.Request to the wire,
bypassing the http.Client. In this scenario we are by default using
HTTP/1.1 from both the client and to the target server we are proxying.
Because of this we must disable HTTP2 in the TLS handshake so that the
server does not think we are writing a HTTP2 request. We do this by
setting the TLSConfig.NextProtos field to "http/1.1".

fix #88782

NONE

@k8s-ci-robot k8s-ci-robot added size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-kind Indicates a PR lacks a `kind/foo` label and requires one. needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. labels Mar 3, 2020
@k8s-ci-robot
Copy link
Contributor

Hi @ibuildthecloud. Thanks for your PR.

I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@k8s-ci-robot k8s-ci-robot added needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. and removed needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Mar 3, 2020
@ibuildthecloud
Copy link
Contributor Author

Fixes #88782

@liggitt
Copy link
Member

liggitt commented Mar 4, 2020

Thanks, can we exercise the bug and fix with a test?

@mikedanese
Copy link
Member

Doesn't this diable http/2 for all proxied requests? Why not just use:

// UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided.
// This allows clients to disable HTTP/2.
UpgradeTransport UpgradeRequestRoundTripper

And pass in a transport with http/2 disabled?

@ibuildthecloud
Copy link
Contributor Author

@mikedanese This disables http2 for only times that we use GetDialer which is only when we are handling a connection upgrade. Correct me if I'm wrong, but connection upgrade is a http/1.1 concept so it is required to disable http2. If the user passes in a transport to disable http2 that will disable http2 for all proxied request. We'd prefer to use http2 on http2 compatible requests. This approach disables http2 only when we know it is explicitly disallowed.

@ibuildthecloud
Copy link
Contributor Author

@liggitt will add test.

@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Mar 4, 2020
@ibuildthecloud
Copy link
Contributor Author

@liggitt Updated with tests.

@mikedanese
Copy link
Member

Yes, we shouldn't be using http/2 for upgrade requests. I thought we hit it here:

But that function is unused now. This is all pretty messy at this point and is due for some cleanup.

// going to write HTTP/1.1 request to the wire. http2 should not be allowed in the TLSConfig.NextProtos,
// so we explicitly set that here. We only do this check if the TLSConfig support http/1.1.
if supportsHTTP11(tlsConfig.NextProtos) {
tlsConfig.NextProtos = []string{"http/1.1"}
Copy link
Member

Choose a reason for hiding this comment

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

The above block tries hard not to modify the tlsConfig that is passed in. Do we need to make a copy here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're correct, I thought it was already copied or new at this point but I see I'm wrong.

@mikedanese
Copy link
Member

Can we either:

  • Document that DialURL won't negotiate http/2 with ALPN.
  • Make DialURL private since we don't use it anywhere else.

Alternatively, we could probably fix this outside of dial url by making sure that DialForUpgrade only uses a transport that has http/2 disabled.

@ibuildthecloud
Copy link
Contributor Author

@mikedanese I added documentation to the DialURL method to describe it's behavior regarding http2 and http/1.1.

@@ -355,6 +355,25 @@ func TestProxyUpgrade(t *testing.T) {
ServerFunc: httptest.NewServer,
ProxyTransport: nil,
},
"booth client and server support http2, but force to http/1.1 for upgrade": {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"booth client and server support http2, but force to http/1.1 for upgrade": {
"both client and server support http2, but force to http/1.1 for upgrade": {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

@@ -355,6 +355,25 @@ func TestProxyUpgrade(t *testing.T) {
ServerFunc: httptest.NewServer,
ProxyTransport: nil,
},
"booth client and server support http2, but force to http/1.1 for upgrade": {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"booth client and server support http2, but force to http/1.1 for upgrade": {
"both client and server support http2, but force to http/1.1 for upgrade": {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

@@ -81,6 +86,15 @@ func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (ne
tlsConfigCopy.ServerName = inferredHost
tlsConfig = tlsConfigCopy
}

// Since this method is primary used within a "Connection: Upgrade" call we assume the caller is
Copy link
Member

Choose a reason for hiding this comment

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

What does "Connection: Upgrade" mean? Is it a term?

Copy link
Member

Choose a reason for hiding this comment

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

https://tools.ietf.org/html/rfc7230#section-6.7

We upgrade http requests to websocket or spdy for streaming request like exec and logs. The headers communicates that you are switching protocols from http1/1 to X so when the upgrade is complete you can send whatever over it like it's just a tcp connection. Sending an upgrade request over http/2 is a connection error because underlying tcp connections are shared between requests. We can eventually migrate off of http1/1 altogether but we are waiting for go support to land. golang/go#27244 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Thanks. It's an http header.
nit: should be "Connection: upgrade" according to the RFC.

// RoundTripper. The primary use of this method is to support proxying upgradable connections.
// For this reason this method will prefer to negotiate http/1.1 if the URL scheme is https.
// If you wish to ensure ALPN negotiates http2 then set NextProto=[]string{"http2"} in the
// TLSConfig of the http.Transport
func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (net.Conn, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's make it private if it doesn't k/k.

@caesarxuchao
Copy link
Member

In the PR description (the first comment), can you add "fix #88782"?

@mikedanese
Copy link
Member

are you saying the UpgradeAwareHandler.Dial is not being used anymore?

Yup, I was kind of confused since it's the only thing that might dial for no upgrade requests so I deleted it and did a full compile. No usage apparently.

@ibuildthecloud
Copy link
Contributor Author

I removed UpgradeAwareHandler.Dial and renamed DialURL to be private.

When proxying connection upgrade requests, like websockets, we dial
the target and then manually write the http.Request to the wire,
bypassing the http.Client.  In this scenario we are by default using
HTTP/1.1 from both the client and to the target server we are proxying.
Because of this we must disable HTTP2 in the TLS handshake so that the
server does not think we are writing a HTTP2 request. We do this by
setting the TLSConfig.NextProtos field to "http/1.1".

Signed-off-by: Darren Shepherd <darren@rancher.com>
@mikedanese
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 Mar 6, 2020
@mikedanese mikedanese added this to the v1.18 milestone Mar 6, 2020
@mikedanese
Copy link
Member

Tentatively tagged for 1.18 since it fixes a bug and we haven't blown the final whistle yet.

@mikedanese
Copy link
Member

/retest

1 similar comment
@mikedanese
Copy link
Member

/retest

@caesarxuchao
Copy link
Member

This lgtm, too.

@caesarxuchao
Copy link
Member

/test pull-kubernetes-e2e-gce-network-proxy

Run the tests since this is on the path of the apiserver network proxy. Though it won't pass until #88863 gets merged

@mikedanese
Copy link
Member

@caesarxuchao still needs approval.

@caesarxuchao
Copy link
Member

/test pull-kubernetes-e2e-gce-network-proxy

@caesarxuchao
Copy link
Member

/approve
/hold

I want to make sure pull-kubernetes-e2e-gce-network-proxy passes before merging this.

This is a bug fix so I think it can merge after code freeze.

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Mar 6, 2020
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: caesarxuchao, ibuildthecloud

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 Mar 6, 2020
@caesarxuchao
Copy link
Member

@mikedanese @ibuildthecloud feel free to remove the hold if pull-kubernetes-e2e-gce-network-proxy passes.

@mikedanese mikedanese removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Mar 6, 2020
@k8s-ci-robot k8s-ci-robot merged commit 7c7ce47 into kubernetes:master Mar 6, 2020
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. 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. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. release-note-none Denotes a PR that doesn't merit a release note. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proxying websocket to http2 capable server failed with malformed response
7 participants