Skip to content

Commit

Permalink
Allow forcing X-Forwarded-Proto Header
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
timmow authored and Jonty committed Sep 14, 2016
1 parent 394ea17 commit a42f716
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 5 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
16 changes: 11 additions & 5 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type ProxyArgs struct {
Logger lager.Logger
HealthCheckUserAgent string
EnableZipkin bool
ForceForwardedProtoHttps bool
}

type proxyHandler struct {
Expand Down Expand Up @@ -90,6 +91,7 @@ type proxy struct {
extraHeadersToLog *[]string
routeServiceRecommendHttps bool
healthCheckUserAgent string
forceForwardedProtoHttps bool
}

func NewProxy(args ProxyArgs) Proxy {
Expand Down Expand Up @@ -126,6 +128,7 @@ func NewProxy(args ProxyArgs) Proxy {
extraHeadersToLog: args.ExtraHeadersToLog,
routeServiceRecommendHttps: args.RouteServiceRecommendHttps,
healthCheckUserAgent: args.HealthCheckUserAgent,
forceForwardedProtoHttps: args.ForceForwardedProtoHttps,
}

n.UseHandler(p)
Expand Down Expand Up @@ -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()
Expand All @@ -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,
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions proxy/proxy_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
32 changes: 32 additions & 0 deletions proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit a42f716

Please sign in to comment.