Skip to content

Honor X-Forwarded-Port header - #2008

Merged
dmcgowan merged 1 commit into
distribution:masterfrom
miminar:honor-x-forwarded-port
Nov 2, 2016
Merged

Honor X-Forwarded-Port header#2008
dmcgowan merged 1 commit into
distribution:masterfrom
miminar:honor-x-forwarded-port

Conversation

@miminar

@miminar miminar commented Oct 17, 2016

Copy link
Copy Markdown
Contributor

If the explicit port is specified using this header, use it as a base for redirect urls.

As documented in article "HTTP Headers and Elastic Load Balancing" of AWS ELB docs:

The X-Forwarded-Port request header helps you identify the port that an HTTP or HTTPS load balancer uses to connect to the client.

This makes registry behave correctly when proxy strips port from the requests e.g.:

GET /openshift/token?account=unused&scope=repository%3Apjoe%2Fhello-world%3Apush%2Cpull HTTP/1.1
Host: registry.f24-ose.vm
User-Agent: docker/1.12.1 go/go1.7 git-commit/23cf638 kernel/4.7.6-1-ARCH os/linux arch/amd64 UpstreamClient(Docker-Client/1.12.1 \(linux\))
Authorization: Basic dW51c2VkOlV1WGoyTGdTWkk4TGc0azY1bXFwc3ZlT1VKdXg1NnFDMTJXMThRdUdYOG8=
Accept-Encoding: gzip
Connection: close
X-Forwarded-Host: registry.f24-ose.vm
X-Forwarded-Port: 5000
X-Forwarded-Proto: http
Forwarded: for=192.168.100.80;host=registry.f24-ose.vm;proto=http
X-Forwarded-For: 192.168.100.80

Where original request (docker daemon -> registry's proxy) looks like this:

GET /openshift/token?account=unused&scope=repository%3Apjoe%2Fhello-world%3Apush%2Cpull HTTP/1.1
Host: registry.f24-ose.vm:5000
User-Agent: docker/1.12.1 go/go1.7 git-commit/23cf638 kernel/4.7.6-1-ARCH os/linux arch/amd64 UpstreamClient(Docker-Client/1.12.1 \(linux\))
Authorization: Basic dW51c2VkOlV1WGoyTGdTWkk4TGc0azY1bXFwc3ZlT1VKdXg1NnFDMTJXMThRdUdYOG8=
Accept-Encoding: gzip
Connection: close

Without this patch, during a POST /v2/pjoe/hello-world/blobs/uploads/ HTTP/1.1 request, registry would redirect to port-less url registry.f24-ose.vm which would make docker daemon attempt to upload on port 80 and fail.

With this patch, registry will reply with correct location:

HTTP/1.1 202 Accepted
Content-Length: 0
Docker-Distribution-Api-Version: registry/2.0
Docker-Upload-Uuid: 24f37d8f-312d-45d4-b507-5a45923daf14
Location: http://registry.f24-ose.vm:5000/v2/pjoe/hello-world/blobs/uploads/24f37d8f-312d-45d4-b507-5a45923daf14?_state=g0583DtjmtCDcE3a8fSJbTwoM70F5BL15Omx058lFLx7Ik5hbWUiOiJwam9lL2hlbGxvLXdvcmxkIiwiVVVJRCI6IjI0ZjM3ZDhmLTMxMmQtNDVkNC1iNTA3LTVhNDU5MjNkYWYxNCIsIk9mZnNldCI6MCwiU3RhcnRlZEF0IjoiMjAxNi0xMC0xN1QxMjoxNzowMC42MTYwNDU2NzhaIn0%3D
...

@miminar miminar changed the title Honor X-Forwarded-Port header [Do not merge] Honor X-Forwarded-Port header Oct 17, 2016
@miminar miminar changed the title [Do not merge] Honor X-Forwarded-Port header Honor X-Forwarded-Port header Oct 17, 2016
@codecov-io

codecov-io commented Oct 17, 2016

Copy link
Copy Markdown

Current coverage is 51.26% (diff: 94.17%)

Merging #2008 into master will decrease coverage by 9.88%

@@             master      #2008   diff @@
==========================================
  Files           125        126      +1   
  Lines         11136      11240    +104   
  Methods           0          0           
  Messages          0          0           
  Branches          0          0           
==========================================
- Hits           6809       5762   -1047   
- Misses         3443       4730   +1287   
+ Partials        884        748    -136   

Powered by Codecov. Last update 8234784...eb1c254

@RichardScothern

Copy link
Copy Markdown

Could you rebase to get the build fix please @miminar

Comment thread registry/api/v2/urls.go Outdated
host = strings.TrimSpace(hosts[0])
}

forwardedPort := r.Header.Get("X-Forwarded-Port")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Isn't port a part of the host? How do these work together?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Mostly it is. These headers aren't defined by any standard, so it's hard to give a precise meaning. Nevertheless, when proxy sets the X-Forwarded-Port it is an explicit proxy's port the client sent request to.

When X-Forwarded-Host lacks port, the explicit port shall be taken from X-Forwarded-Port if set. I'm not that sure about conflicting ports in both headers but I take explicit value as decisive.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

But, a port in the Host header is explicit, in that it is guaranteed to be set by a single entity, whereas X-Forward-Port may have been set by a secondary party.

Comment thread registry/api/v2/urls_test.go Outdated
forwardedComboHeader.Set("X-Forwarded-Port", " 12345 \t")

forwardedMixedHeader := make(http.Header, 2)
forwardedMixedHeader.Set("X-Forwarded-Host", "first.example.com:5000")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This example concerns me. We clearly have a declared port in the host header but we now trust an unbound port? How do we know the same proxy added both headers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think there's right or wrong answer to this. I find explicit value as better.

If there are two proxies, one overriding X-Forwarded-Host and the other overriding X-Forwarded-Port with conflicting ports, it is a clear misconfiguration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@miminar Should we not honor the "complete" host, rather than overriding the port? It seems like port should only apply if the host lacks a port but I am not sure what is expected. From a security perspective, the host must include a port, but I am not sure about how these headers are supposed to work.

@stevvooe

Copy link
Copy Markdown
Collaborator

This makes registry behave correctly when proxy strips port from the requests e.g.:

Why are proxies stripping ports from requests?

@miminar
miminar force-pushed the honor-x-forwarded-port branch from bd7aa9b to ade8341 Compare October 18, 2016 08:16
@miminar

miminar commented Oct 18, 2016

Copy link
Copy Markdown
Contributor Author

Why are proxies stripping ports from requests?

I cannot speak for all proxies. The default haproxy deployed by OpenShift strips the port from host headers as it is mostly running on default ones. If deployed to run on non-default port, there's still X-Forwarded-Port to let clients do proper redirects.

I don't know if it's the right approach. Keeping the port in host headers for non-default ports would certainly work as well. Nevertheless, honoring this header on application level, won't make the app worse.

@stevvooe

Copy link
Copy Markdown
Collaborator

@miminar Ok, so it sounds like we should honor the X-Forwarded-Port header if the X-Forwarded-Host header does not specify a port. Does that sound reasonable?

Hopefully, you can fix the bug in openshift which is stripping the ports.

Also, it looks like the implementation of Forwarded in your examples is not following the rfc7230, in that it is missing a port. We probably need to add support for that header, as well.

@RichardScothern

Copy link
Copy Markdown

@miminar are these the docs you refer to? What about the x-forwarded-for header? (there is code in the context package for extracting this)

@miminar

miminar commented Oct 19, 2016

Copy link
Copy Markdown
Contributor Author

Ok, so it sounds like we should honor the X-Forwarded-Port header if the X-Forwarded-Host header does not specify a port. Does that sound reasonable?

It does, thanks!

Also, it looks like the implementation of Forwarded in your examples is not following the rfc7230, in that it is missing a port. We probably need to add support for that header, as well.

Do you want to follow-up on that or shall I include it in this PR?

are these the docs you refer to? What about the x-forwarded-for header? (there is code in the context package for extracting this)

Yes. I'll include the support for X-Forwarded-For as well. Hopefully tomorrow.

@stevvooe

Copy link
Copy Markdown
Collaborator

Do you want to follow-up on that or shall I include it in this PR?

That is up to you. It seems orthogonal.

Thanks for taking care of this!

@miminar
miminar force-pushed the honor-x-forwarded-port branch from ade8341 to 8a7f011 Compare October 22, 2016 12:53
@miminar

miminar commented Oct 22, 2016

Copy link
Copy Markdown
Contributor Author

Included support for standard Forwarded header. The non-standard (X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port) still take precedence. The port set by X-Forwarded-Port is now used only if the port is unset by (forwarded) host.

What about the x-forwarded-for header? (there is code in the context package for extracting this)

@RichardScothern I see the X-Forwarded-For header being already handled by the context. My understanding is that it should be used in logging to identify client, which is already the case. Am I missing something?

@miminar
miminar force-pushed the honor-x-forwarded-port branch from 8a7f011 to f9767b0 Compare October 22, 2016 13:03
Comment thread registry/api/v2/urls.go Outdated
return appendValuesURL(up, values...).String()
}

// Following are states of forwarded header parser.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Put this in a separate file.

Comment thread registry/api/v2/urls.go Outdated
//
// States marked with '*' are terminating. Any state could transition to a
// a failure which isn't listed among states.
const (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are these constants used outside of the parsing function? May just want to define them local to the function.

Comment thread registry/api/v2/urls_test.go Outdated
}
}

func newHTTPRequest(t *testing.T, u *url.URL, headers ...string) *http.Request {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just use a map literal. No need to create another way to specify an http request.

@miminar
miminar force-pushed the honor-x-forwarded-port branch 2 times, most recently from 0c34ef7 to eb1c254 Compare October 25, 2016 10:20
@miminar

miminar commented Oct 25, 2016

Copy link
Copy Markdown
Contributor Author

Comments addressed.

@stevvooe

Copy link
Copy Markdown
Collaborator

LGTM

@dmcgowan

dmcgowan commented Oct 26, 2016

Copy link
Copy Markdown
Collaborator

Parser logic looks good, since there is no unit tests directly with the regex, I would at least like to see the IPv6 case tested, with and without a port.

@miminar
miminar force-pushed the honor-x-forwarded-port branch from eb1c254 to 3ebbd24 Compare October 31, 2016 09:49
@miminar

miminar commented Oct 31, 2016

Copy link
Copy Markdown
Contributor Author

@dmcgowan Added support for ipv6 addresses and tests. I haven't tried to forward ipv6 address though.

@RichardScothern

Copy link
Copy Markdown

@miminar can you check the test failures please

@miminar

miminar commented Nov 2, 2016

Copy link
Copy Markdown
Contributor Author

Ouch, the test passes for me with go 1.7. It looks like I need to tune it for go 1.6.

@miminar
miminar force-pushed the honor-x-forwarded-port branch from 3ebbd24 to c621615 Compare November 2, 2016 15:31
@miminar

miminar commented Nov 2, 2016

Copy link
Copy Markdown
Contributor Author

It should pass on both go16 and go17 now.

Prefer non-standard headers like X-Forwarded-Proto, X-Forwarded-Host and
X-Forwarded-Port over the standard Forwarded header to maintain
backwards compatibility.

If a port is not specified neither in Host nor in forwarded headers but
it is specified just with X-Forwarded-Port, use its value in base urls
for redirects.

Forwarded header is defined in rfc7239.

X-Forwarded-Port is a non-standard header. Here's a description copied
from "HTTP Headers and Elastic Load Balancing" of AWS ELB docs:

> The X-Forwarded-Port request header helps you identify the port that
> an HTTP or HTTPS load balancer uses to connect to the client.

Signed-off-by: Michal Minář <miminar@redhat.com>
@miminar
miminar force-pushed the honor-x-forwarded-port branch from c621615 to 1b43e1e Compare November 2, 2016 15:49
@RichardScothern

Copy link
Copy Markdown

thanks @miminar LGTM

@dmcgowan

dmcgowan commented Nov 2, 2016

Copy link
Copy Markdown
Collaborator

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants