Skip to content

Commit

Permalink
feat: hack: support the "Partitioned" flag on VCAP_ID cookies
Browse files Browse the repository at this point in the history
The `Partitioned` flag is used for cookies that are set on web sites
embedded via iframes. The cookie is then available only in combination
of the host site and the embedded site.

Golang's `http.Cookie` type does not yet support the `Partitioned`
flag, but Google Chrome is already testing mandatory support and
rejecting/omitting cookies without it for 1% of users via A/B roll-out.

The implementation wraps the `http.Cookie` and extends it with the
`Partitioned` field. This field is then used to forward the raw
cookie string when creating the derived VCAP_ID cookies for sticky
sessions.

Once the Golang standard library supports the `Partitioned` flag, this
wrapper can just be removed.

A test that checks the `Unparsed` section of the `http.Cookie` will
ensure that the tests will fail once the `Partitioned` flag is
supported by the Golang standard library.
  • Loading branch information
peanball authored and maxmoehl committed Feb 20, 2024
1 parent 4097f31 commit 9015251
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 17 deletions.
42 changes: 33 additions & 9 deletions proxy/round_tripper/proxy_round_tripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http/httptrace"
"net/textproto"
"net/url"
"slices"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -61,6 +62,20 @@ func GetRoundTripper(endpoint *route.Endpoint, roundTripperFactory RoundTripperF
return endpoint.RoundTripper()
}

type Cookie struct {
http.Cookie
// indicates, whether this cookie is partitioned. Relevant for embedding in iframes.
Partitioned bool
}

func (c *Cookie) String() string {
cookieString := c.Cookie.String()
if c.Partitioned {
return cookieString + "; Partitioned"
}
return cookieString
}

//go:generate counterfeiter -o fakes/fake_error_handler.go --fake-name ErrorHandler . errorHandler
type errorHandler interface {
HandleError(utils.ProxyResponseWriter, error)
Expand Down Expand Up @@ -399,6 +414,7 @@ func setupStickySession(
maxAge := 0
sameSite := http.SameSite(0)
expiry := time.Time{}
partitioned := false

if responseContainsAuthNegotiateHeader && authNegotiateSticky {
maxAge = AuthNegotiateHeaderCookieMaxAgeInSeconds
Expand All @@ -414,6 +430,11 @@ func setupStickySession(
secure = v.Secure
sameSite = v.SameSite
expiry = v.Expires

// temporary workaround for "Partitioned" cookies, used in embedded websites (iframe),
// until Golang natively supports parsing the Partitioned flag.
// See also https://github.com/golang/go/issues/62490
partitioned = slices.Contains(v.Unparsed, "Partitioned")
break
}
}
Expand All @@ -433,15 +454,18 @@ func setupStickySession(
secure = true
}

vcapIDCookie := &http.Cookie{
Name: VcapCookieId,
Value: endpoint.PrivateInstanceId,
Path: path,
MaxAge: maxAge,
HttpOnly: true,
Secure: secure,
SameSite: sameSite,
Expires: expiry,
vcapIDCookie := &Cookie{
Cookie: http.Cookie{
Name: VcapCookieId,
Value: endpoint.PrivateInstanceId,
Path: path,
MaxAge: maxAge,
HttpOnly: true,
Secure: secure,
SameSite: sameSite,
Expires: expiry,
},
Partitioned: partitioned,
}

if v := vcapIDCookie.String(); v != "" {
Expand Down
26 changes: 18 additions & 8 deletions proxy/round_tripper/proxy_round_tripper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@ var _ = Describe("ProxyRoundTripper", func() {

Context("when using sticky sessions", func() {
var (
sessionCookie *http.Cookie
sessionCookie *round_tripper.Cookie
endpoint1 *route.Endpoint
endpoint2 *route.Endpoint

Expand All @@ -1018,6 +1018,7 @@ var _ = Describe("ProxyRoundTripper", func() {
sessionCookie.Expires = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)
sessionCookie.Secure = true
sessionCookie.HttpOnly = true
sessionCookie.Partitioned = true
}

sessionCookie.Value, _ = uuid.GenerateUUID()
Expand All @@ -1026,9 +1027,11 @@ var _ = Describe("ProxyRoundTripper", func() {
}

setVCAPID := func(resp *http.Response) (response *http.Response) {
vcapCookie := http.Cookie{
Name: round_tripper.VcapCookieId,
Value: "vcap-id-property-already-on-the-response",
vcapCookie := round_tripper.Cookie{
Cookie: http.Cookie{
Name: round_tripper.VcapCookieId,
Value: "vcap-id-property-already-on-the-response",
},
}

if c := vcapCookie.String(); c != "" {
Expand Down Expand Up @@ -1072,8 +1075,10 @@ var _ = Describe("ProxyRoundTripper", func() {
}

BeforeEach(func() {
sessionCookie = &http.Cookie{
Name: StickyCookieKey, //JSESSIONID
sessionCookie = &round_tripper.Cookie{
Cookie: http.Cookie{
Name: StickyCookieKey, //JSESSIONID
},
}

endpoint1 = route.NewEndpoint(&route.EndpointOpts{
Expand Down Expand Up @@ -1365,10 +1370,15 @@ var _ = Describe("ProxyRoundTripper", func() {
newCookies := resp.Cookies()
Expect(newCookies).To(HaveLen(2))
Expect(newCookies[0].Raw).To(Equal(sessionCookie.String()))

// This should fail when Golang introduces parsing for the Partitioned flag on cookies.
// see https://github.com/golang/go/issues/62490
Expect(newCookies[0].Unparsed).To(Equal([]string{"Partitioned"}))

Expect(newCookies[1].Name).To(Equal(round_tripper.VcapCookieId))
Expect(newCookies[1].Value).To(Equal(cookies[1].Value)) // still pointing to the same app
Expect(sessionCookie.String()).To(ContainSubstring("Expires=Wed, 01 Jan 2020 01:00:00 GMT; HttpOnly; Secure; SameSite=Strict"))
Expect(newCookies[1].Raw).To(ContainSubstring("Expires=Wed, 01 Jan 2020 01:00:00 GMT; HttpOnly; Secure; SameSite=Strict"))
Expect(sessionCookie.String()).To(ContainSubstring("Expires=Wed, 01 Jan 2020 01:00:00 GMT; HttpOnly; Secure; SameSite=Strict; Partitioned"))
Expect(newCookies[1].Raw).To(ContainSubstring("Expires=Wed, 01 Jan 2020 01:00:00 GMT; HttpOnly; Secure; SameSite=Strict; Partitioned"))
})
})

Expand Down

0 comments on commit 9015251

Please sign in to comment.