Skip to content

Commit b4be29b

Browse files
authored
Add Forwarded headers to the fetch request. (#382)
To be cautious re: privacy/security, specify only host. Note that this changes behavior for anybody who was previously including "Forwarded" or "X-Forwarded-Host" in their ForwardedRequestHeaders in the config.
1 parent 58d2a98 commit b4be29b

File tree

4 files changed

+73
-0
lines changed

4 files changed

+73
-0
lines changed

packager/signer/signer.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,17 @@ func (this *Signer) fetchURL(fetch *url.URL, serveHTTPReq *http.Request) (*http.
177177
}
178178
req.Header.Set("Via", via)
179179
}
180+
if quotedHost, err := util.QuotedString(serveHTTPReq.Host); err == nil {
181+
// TODO(twifkak): Extract host from upstream Forwarded header
182+
// and concatenate. (Do not include any other parameters, as
183+
// they may lead to over-signing.)
184+
req.Header.Set("Forwarded", `host=` + quotedHost)
185+
xfh := serveHTTPReq.Host
186+
if oldXFH := serveHTTPReq.Header.Get("X-Forwarded-Host"); oldXFH != "" {
187+
xfh = oldXFH + "," + xfh
188+
}
189+
req.Header.Set("X-Forwarded-Host", xfh)
190+
}
180191
// Set conditional headers that were included in ServeHTTP's Request.
181192
for header := range util.ConditionalRequestHeaders {
182193
if value := GetJoined(serveHTTPReq.Header, header); value != "" {

packager/signer/signer_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ func (this *SignerSuite) TestSimple() {
183183
this.Assert().Equal(fakePath, this.lastRequest.URL.String())
184184
this.Assert().Equal(userAgent, this.lastRequest.Header.Get("User-Agent"))
185185
this.Assert().Equal("1.1 amppkg", this.lastRequest.Header.Get("Via"))
186+
this.Assert().Equal(`host="example.com"`, this.lastRequest.Header.Get("Forwarded"))
187+
this.Assert().Equal("example.com", this.lastRequest.Header.Get("X-Forwarded-Host"))
186188
this.Assert().Equal(http.StatusOK, resp.StatusCode, "incorrect status: %#v", resp)
187189
this.Assert().Equal(fmt.Sprintf(`google;v="%d"`, transformer.SupportedVersions[0].Max), resp.Header.Get("AMP-Cache-Transform"))
188190
this.Assert().Equal("nosniff", resp.Header.Get("X-Content-Type-Options"))
@@ -272,6 +274,25 @@ func (this *SignerSuite) TestFetchSignWithForwardedRequestHeaders() {
272274
this.Assert().Equal(append(payloadPrefix.Bytes(), transformedBody...), exchange.Payload)
273275
}
274276

277+
func (this *SignerSuite) TestForwardedHost() {
278+
urlSets := []util.URLSet{{
279+
Sign: &util.URLPattern{[]string{"https"}, "", this.httpHost(), stringPtr("/amp/.*"), []string{}, stringPtr(""), false, 2000, nil},
280+
Fetch: &util.URLPattern{[]string{"http"}, "", this.httpHost(), stringPtr("/amp/.*"), []string{}, stringPtr(""), false, 2000, boolPtr(true)},
281+
}}
282+
header := http.Header{
283+
"AMP-Cache-Transform": {"google"}, "Accept": {"application/signed-exchange;v=" + accept.AcceptedSxgVersion},
284+
"Forwarded": {`host="www.example.com";for=192.0.0.1`},
285+
"X-Forwarded-For": {"192.0.0.1"},
286+
"X-Forwarded-Host": {"www.example.com"}}
287+
this.getFRH(this.T(), this.new(urlSets),
288+
"/priv/doc?fetch="+url.QueryEscape(this.httpURL()+fakePath)+
289+
"&sign="+url.QueryEscape(this.httpSignURL()+fakePath),
290+
"example.com", header)
291+
292+
this.Assert().Equal(`host="example.com"`, this.lastRequest.Header.Get("Forwarded"))
293+
this.Assert().Equal("www.example.com,example.com", this.lastRequest.Header.Get("X-Forwarded-Host"))
294+
}
295+
275296
func (this *SignerSuite) TestEscapeQueryParamsInFetchAndSign() {
276297
urlSets := []util.URLSet{{
277298
Sign: &util.URLPattern{[]string{"https"}, "", this.httpHost(), stringPtr("/amp/.*"), []string{}, stringPtr(".*"), false, 2000, nil},

packager/util/http.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"net/http"
66
"regexp"
77
"strings"
8+
9+
"github.com/pkg/errors"
810
)
911

1012
// A comma, as defined in https://tools.ietf.org/html/rfc7230#section-7, with
@@ -91,3 +93,26 @@ func haveInvalidForwardedRequestHeader(h string) string {
9193
}
9294
return ""
9395
}
96+
97+
// Escapes the input and surrounds it in quotes, so it's a valid quoted-string,
98+
// per https://tools.ietf.org/html/rfc7230#section-3.2.6. Returns error if the
99+
// input contains any chars outside of HTAB / SP / VCHAR
100+
// (https://tools.ietf.org/html/rfc5234#appendix-B.1) and thus isn't even
101+
// quotable.
102+
func QuotedString(input string) (string, error) {
103+
var ret strings.Builder
104+
ret.WriteByte('"')
105+
for i := 0; i < len(input); i++ {
106+
b := input[i]
107+
if (b < 0x20 || b > 0x7e) && b != 0x09 {
108+
return "", errors.New("contains non-printable char")
109+
}
110+
if b == '"' || b == '\\' {
111+
ret.Write([]byte{'\\', b})
112+
} else {
113+
ret.WriteByte(b)
114+
}
115+
}
116+
ret.WriteByte('"')
117+
return ret.String(), nil
118+
}

packager/util/http_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package util
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestQuotedString(t *testing.T) {
10+
valueFrom := func(s string, _ error) string { return s }
11+
errorFrom := func(_ string, err error) error { return err }
12+
13+
assert.EqualError(t, errorFrom(QuotedString("abc\ndef")), "contains non-printable char")
14+
assert.Equal(t, `"abc"`, valueFrom(QuotedString("abc")))
15+
assert.Equal(t, `"abc\"\\"`, valueFrom(QuotedString(`abc"\`)))
16+
}

0 commit comments

Comments
 (0)