Skip to content

Commit

Permalink
Issue #280: Extend Forwarded header with protocol data
Browse files Browse the repository at this point in the history
This patch adds an 'httpproto' field which contains the lower case
version of the HTTP protocol to the Forwarded header of all upstream
requests.

When fabio terminates the TLS connection two additional fields 'tlsver'
and 'tlscipher' are added to the Forwarded field. 'tlsver' contains the
TLS version as 'tlscipher' the chosen cipher suite. The cipher suite is
formatted as a hex number from
https://golang.org/pkg/crypto/tls/#pkg-constants.

Fixes #280
  • Loading branch information
magiconair committed Jun 6, 2017
1 parent 716c337 commit 5febab6
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 0 deletions.
36 changes: 36 additions & 0 deletions proxy/http_headers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package proxy

import (
"crypto/tls"
"errors"
"net"
"net/http"
Expand Down Expand Up @@ -73,6 +74,19 @@ func addHeaders(r *http.Request, cfg config.Proxy) error {
if cfg.LocalIP != "" {
fwd += "; by=" + cfg.LocalIP
}
if r.Proto != "" {
fwd += "; httpproto=" + strings.ToLower(r.Proto)
}
if r.TLS != nil && r.TLS.Version > 0 {
v := tlsver[r.TLS.Version]
if v == "" {
v = uint16base16(r.TLS.Version)
}
fwd += "; tlsver=" + v
}
if r.TLS != nil && r.TLS.CipherSuite != 0 {
fwd += "; tlscipher=" + uint16base16(r.TLS.CipherSuite)
}
r.Header.Set("Forwarded", fwd)

if cfg.TLSHeader != "" {
Expand All @@ -86,6 +100,28 @@ func addHeaders(r *http.Request, cfg config.Proxy) error {
return nil
}

var tlsver = map[uint16]string{
tls.VersionSSL30: "ssl30",
tls.VersionTLS10: "tls10",
tls.VersionTLS11: "tls11",
tls.VersionTLS12: "tls12",
}

var digit16 = []byte("0123456789abcdef")

// uint16base64 is a faster version of fmt.Sprintf("0x%04x", n)
//
// BenchmarkUint16Base16/fmt.Sprintf-8 10000000 154 ns/op 8 B/op 2 allocs/op
// BenchmarkUint16Base16/uint16base16-8 50000000 35.0 ns/op 8 B/op 1 allocs/op
func uint16base16(n uint16) string {
b := []byte("0x0000")
b[5] = digit16[n&0x000f]
b[4] = digit16[n&0x00f0>>4]
b[3] = digit16[n&0x0f00>>8]
b[2] = digit16[n&0xf000>>12]
return string(b)
}

// scheme derives the request scheme used on the initial
// request first from headers and then from the connection
// using the following heuristic:
Expand Down
47 changes: 47 additions & 0 deletions proxy/http_headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package proxy

import (
"crypto/tls"
"fmt"
"net/http"
"testing"

Expand Down Expand Up @@ -113,6 +114,30 @@ func TestAddHeaders(t *testing.T) {
"",
},

{"set httpproto, tlsver and tlscipher on Forwarded for https",
&http.Request{RemoteAddr: "1.2.3.4:5555", Proto: "HTTP/1.1", TLS: &tls.ConnectionState{Version: tls.VersionTLS10, CipherSuite: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}},
config.Proxy{},
http.Header{
"Forwarded": []string{"for=1.2.3.4; proto=https; httpproto=http/1.1; tlsver=tls10; tlscipher=0xc023"},
"X-Forwarded-Proto": []string{"https"},
"X-Forwarded-Port": []string{"443"},
"X-Real-Ip": []string{"1.2.3.4"},
},
"",
},

{"set httpproto on Forwarded",
&http.Request{RemoteAddr: "1.2.3.4:5555", Proto: "HTTP/1.1"},
config.Proxy{},
http.Header{
"Forwarded": []string{"for=1.2.3.4; proto=http; httpproto=http/1.1"},
"X-Forwarded-Proto": []string{"http"},
"X-Forwarded-Port": []string{"80"},
"X-Real-Ip": []string{"1.2.3.4"},
},
"",
},

{"extend Forwarded with localIP",
&http.Request{RemoteAddr: "1.2.3.4:5555", Header: http.Header{"Forwarded": {"for=9.9.9.9; proto=http; by=8.8.8.8"}}},
config.Proxy{LocalIP: "5.6.7.8"},
Expand Down Expand Up @@ -327,3 +352,25 @@ func TestLocalPort(t *testing.T) {
}
}
}

func TestUint16Base16(t *testing.T) {
for i := uint16(0); i <= 9999; i++ {
if got, want := uint16base16(i), fmt.Sprintf("0x%04x", i); got != want {
t.Fatalf("got %q for %04x want %q", got, i, want)
}
}
}

func BenchmarkUint16Base16(b *testing.B) {
var s string
b.Run("fmt.Sprintf", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s = fmt.Sprintf("0x%04x", uint16(i))
}
})
b.Run("uint16base16", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s = uint16base16(uint16(i))
}
})
}

0 comments on commit 5febab6

Please sign in to comment.