From a42f716766a6da9cae809070b9f447390fc4ab0c Mon Sep 17 00:00:00 2001 From: Timothy Mower Date: Tue, 5 Apr 2016 17:41:27 +0100 Subject: [PATCH] Allow forcing X-Forwarded-Proto Header There are situations where it's not possible to determine the appropriate value for this header from the incoming connection (for example when running behind an Amazon ELB in TCP mode where SSL termination is handled by the ELB). This therefore adds a config option to allow forcing the X-Forwarded-Proto header to be https. --- README.md | 12 ++++++++++++ config/config.go | 1 + config/config_test.go | 6 ++++++ main.go | 1 + proxy/proxy.go | 16 +++++++++++----- proxy/proxy_suite_test.go | 1 + proxy/proxy_test.go | 32 ++++++++++++++++++++++++++++++++ 7 files changed, 64 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4eb9a938d..da6ac5546 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,18 @@ echo -e "PROXY TCP4 1.2.3.4 [GOROUTER IP] 12345 [GOROUTER PORT]\r\nGET / HTTP/1. You should see in the access logs on the GoRouter that the `X-Forwarded-For` header is `1.2.3.4`. You can read more about the PROXY Protocol [here](http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt). +## SSL termination & X-Forwarded-Proto + +If you terminate SSL before passing traffic to gorouter it will incorrectly set the `X-Forwarded-Proto` header to `http` instead of `https`, which results in applications incorrectly detecting they are using an insecure connection. + +To force the `X-Forwarded-Proto` header to `https` you can configure your cf-release manifest as follows: + +``` +properties: + router: + force_forwarded_proto_https: true +``` + ## HTTP/2 Support The GoRouter does not currently support proxying HTTP/2 connections, even over TLS. Connections made using HTTP/1.1, either by TLS or cleartext, will be proxied to backends over cleartext. diff --git a/config/config.go b/config/config.go index a01f05472..c28a78b65 100644 --- a/config/config.go +++ b/config/config.go @@ -100,6 +100,7 @@ type Config struct { SSLKeyPath string `yaml:"ssl_key_path"` SSLCertificate tls.Certificate SkipSSLValidation bool `yaml:"skip_ssl_validation"` + ForceForwardedProtoHttps bool `yaml:"force_forwarded_proto_https"` CipherString string `yaml:"cipher_suites"` CipherSuites []uint16 diff --git a/config/config_test.go b/config/config_test.go index 36eb7dbdd..cdf947d65 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -355,6 +355,12 @@ enable_proxy: true Expect(config.Tracing.EnableZipkin).To(BeFalse()) }) + + It("sets the proxy forwarded proto header", func() { + var b = []byte("force_forwarded_proto_https: true") + config.Initialize(b) + Expect(config.ForceForwardedProtoHttps).To(Equal(true)) + }) }) Describe("Process", func() { diff --git a/main.go b/main.go index 804405fdb..0537ff2fa 100644 --- a/main.go +++ b/main.go @@ -182,6 +182,7 @@ func buildProxy(logger lager.Logger, c *config.Config, registry rregistry.Regist ExtraHeadersToLog: &c.ExtraHeadersToLog, HealthCheckUserAgent: c.HealthCheckUserAgent, EnableZipkin: c.Tracing.EnableZipkin, + ForceForwardedProtoHttps: c.ForceForwardedProtoHttps, } return proxy.NewProxy(args) } diff --git a/proxy/proxy.go b/proxy/proxy.go index 6db256572..ef9062a42 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -61,6 +61,7 @@ type ProxyArgs struct { Logger lager.Logger HealthCheckUserAgent string EnableZipkin bool + ForceForwardedProtoHttps bool } type proxyHandler struct { @@ -90,6 +91,7 @@ type proxy struct { extraHeadersToLog *[]string routeServiceRecommendHttps bool healthCheckUserAgent string + forceForwardedProtoHttps bool } func NewProxy(args ProxyArgs) Proxy { @@ -126,6 +128,7 @@ func NewProxy(args ProxyArgs) Proxy { extraHeadersToLog: args.ExtraHeadersToLog, routeServiceRecommendHttps: args.RouteServiceRecommendHttps, healthCheckUserAgent: args.HealthCheckUserAgent, + forceForwardedProtoHttps: args.ForceForwardedProtoHttps, } n.UseHandler(p) @@ -321,7 +324,7 @@ func (p *proxy) ServeHTTP(responseWriter http.ResponseWriter, request *http.Requ roundTripper := round_tripper.NewProxyRoundTripper(backend, dropsonde.InstrumentedRoundTripper(p.transport), iter, handler, after) - newReverseProxy(roundTripper, request, routeServiceArgs, p.routeServiceConfig).ServeHTTP(proxyWriter, request) + newReverseProxy(roundTripper, request, routeServiceArgs, p.routeServiceConfig, p.forceForwardedProtoHttps).ServeHTTP(proxyWriter, request) accessLog.FinishedAt = time.Now() accessLog.BodyBytesSent = proxyWriter.Size() @@ -333,10 +336,11 @@ func (p *proxy) isLoadBalancerHeartbeat(request *http.Request) bool { func newReverseProxy(proxyTransport http.RoundTripper, req *http.Request, routeServiceArgs route_service.RouteServiceArgs, - routeServiceConfig *route_service.RouteServiceConfig) http.Handler { + routeServiceConfig *route_service.RouteServiceConfig, + forceForwardedProtoHttps bool) http.Handler { rproxy := &httputil.ReverseProxy{ Director: func(request *http.Request) { - setupProxyRequest(req, request) + setupProxyRequest(req, request, forceForwardedProtoHttps) handleRouteServiceIntegration(request, routeServiceArgs, routeServiceConfig) }, Transport: proxyTransport, @@ -363,8 +367,10 @@ func handleRouteServiceIntegration( } } -func setupProxyRequest(source *http.Request, target *http.Request) { - if source.Header.Get("X-Forwarded-Proto") == "" { +func setupProxyRequest(source *http.Request, target *http.Request, forceForwardedProtoHttps bool) { + if forceForwardedProtoHttps { + target.Header.Set("X-Forwarded-Proto", "https") + } else if source.Header.Get("X-Forwarded-Proto") == "" { scheme := "http" if source.TLS != nil { scheme = "https" diff --git a/proxy/proxy_suite_test.go b/proxy/proxy_suite_test.go index 64f2b0832..8cceca11c 100644 --- a/proxy/proxy_suite_test.go +++ b/proxy/proxy_suite_test.go @@ -94,6 +94,7 @@ var _ = JustBeforeEach(func() { HealthCheckUserAgent: "HTTP-Monitor/1.1", EnableZipkin: conf.Tracing.EnableZipkin, ExtraHeadersToLog: &conf.ExtraHeadersToLog, + ForceForwardedProtoHttps: conf.ForceForwardedProtoHttps, }) proxyServer, err = net.Listen("tcp", "127.0.0.1:0") diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 026c05d1f..f447a1406 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -625,6 +625,38 @@ var _ = Describe("Proxy", func() { conn.ReadResponse() }) + Context("Force Forwarded Proto HTTPS config option is set", func() { + BeforeEach(func() { + conf.ForceForwardedProtoHttps = true + }) + It("uses config option for X-Forwarded-Proto if present", func() { + done := make(chan string) + + ln := registerHandler(r, "app", func(conn *test_util.HttpConn) { + req, err := http.ReadRequest(conn.Reader) + Expect(err).NotTo(HaveOccurred()) + + resp := test_util.NewResponse(http.StatusOK) + conn.WriteResponse(resp) + conn.Close() + + done <- req.Header.Get("X-Forwarded-Proto") + }) + defer ln.Close() + + conn := dialProxy(proxyServer) + + req := test_util.NewRequest("GET", "app", "/", nil) + conn.WriteRequest(req) + + var answer string + Eventually(done).Should(Receive(&answer)) + Expect(answer).To(Equal("https")) + + conn.ReadResponse() + }) + }) + It("emits HTTP startstop events", func() { ln := registerHandlerWithInstanceId(r, "app", "", func(conn *test_util.HttpConn) { }, "fake-instance-id")