git.iko.soy/typeable/oauth2-proxy
on master help sign in
Allow multiple cookie domains to be specified (#412)

* Allow multiple cookie domains to be specified * Use X-Forwarded-Host, if it exists, when selecting cookie domain * Perform cookie domain sorting in config validation phase * Extract get domain cookies to a single function * Update pkg/cookies/cookies.go Co-Authored-By: Joel Speed <Joel.speed@hotmail.co.uk> * Update changelog Co-authored-by: Marcos Lilljedahl <marcosnils@gmail.com> Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk>

commita659b9558eaa65ed5792b064bc7b3a35f0e80fbe authorEric Dahlseng <edahlseng@users.noreply.github.com> dateSun 12 Apr 2020, 04:00 (7 years ago) parent7f72a22
9 files changed+66−19
CHANGELOG.md+1 docs/configuration/configuration.md+1−1 main.go+2−1 oauthproxy.go+10−8 oauthproxy_test.go+5−5 options.go+7 pkg/apis/options/cookie.go+1−1 pkg/cookies/cookies.go+33−1 pkg/sessions/session_store_test.go+6−2
CHANGELOG.md+1
@@ -33,6 +33,7 @@
3333- [#432](https://github.com/oauth2-proxy/oauth2-proxy/pull/432) Update ruby dependencies for documentation (@theobarberbany)
3434- [#471](https://github.com/oauth2-proxy/oauth2-proxy/pull/471) Add logging in case of invalid redirects (@gargath)
3535- [#462](https://github.com/oauth2-proxy/oauth2-proxy/pull/462) Allow HTML in banner message (@eritikass).
36+- [#412](https://github.com/pusher/oauth2_proxy/pull/412) Allow multiple cookie domains to be specified (@edahlseng)
3637- [#413](https://github.com/oauth2-proxy/oauth2-proxy/pull/413) Add -set-basic-auth param to set the Basic Authorization header for upstreams (@morarucostel).
3738
3839# v5.1.0
docs/configuration/configuration.md+1−1
@@ -33,7 +33,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
3333| `-client-secret` | string | the OAuth Client Secret | |
3434| `-client-secret-file` | string | the file with OAuth Client Secret | |
3535| `-config` | string | path to config file | |
36−| `-cookie-domain` | string | an optional cookie domain to force cookies to (ie: `.yourcompany.com`) | |
36+| `-cookie-domain` | string \| list | Optional cookie domains to force cookies to (ie: `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
3737| `-cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
3838| `-cookie-httponly` | bool | set HttpOnly cookie flag | true |
3939| `-cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
main.go+2−1
@@ -21,6 +21,7 @@ func main() {
2121 logger.SetFlags(logger.Lshortfile)
2222 flagSet := flag.NewFlagSet("oauth2-proxy", flag.ExitOnError)
2323
24+ cookieDomains := StringArray{}
2425 emailDomains := StringArray{}
2526 whitelistDomains := StringArray{}
2627 upstreams := StringArray{}
@@ -87,7 +88,7 @@ func main() {
8788
8889 flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
8990 flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
90− flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
91+ flagSet.Var(&cookieDomains, "cookie-domain", "Optional cookie domains to force cookies to (ie: `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match).")
9192 flagSet.String("cookie-path", "/", "an optional cookie path to force cookies to (ie: /poc/)*")
9293 flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
9394 flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
oauthproxy.go+10−8
@@ -64,7 +64,7 @@ type OAuthProxy struct {
6464 CookieSeed string
6565 CookieName string
6666 CSRFCookieName string
67− CookieDomain string
67+ CookieDomains []string
6868 CookiePath string
6969 CookieSecure bool
7070 CookieHTTPOnly bool
@@ -265,13 +265,13 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
265265 refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
266266 }
267267
268− logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s samesite:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, opts.CookieSameSite, refresh)
268+ logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, strings.Join(opts.CookieDomains, ","), opts.CookiePath, opts.CookieSameSite, refresh)
269269
270270 return &OAuthProxy{
271271 CookieName: opts.CookieName,
272272 CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
273273 CookieSeed: opts.CookieSecret,
274− CookieDomain: opts.CookieDomain,
274+ CookieDomains: opts.CookieDomains,
275275 CookiePath: opts.CookiePath,
276276 CookieSecure: opts.CookieSecure,
277277 CookieHTTPOnly: opts.CookieHTTPOnly,
@@ -377,13 +377,15 @@ func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration
377377}
378378
379379func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
380− if p.CookieDomain != "" {
381− domain := req.Host
380+ cookieDomain := cookies.GetCookieDomain(req, p.CookieDomains)
381+
382+ if cookieDomain != "" {
383+ domain := cookies.GetRequestHost(req)
382384 if h, _, err := net.SplitHostPort(domain); err == nil {
383385 domain = h
384386 }
385− if !strings.HasSuffix(domain, p.CookieDomain) {
386− logger.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
387+ if !strings.HasSuffix(domain, cookieDomain) {
388+ logger.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, cookieDomain)
387389 }
388390 }
389391
@@ -391,7 +393,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
391393 Name: name,
392394 Value: value,
393395 Path: p.CookiePath,
394− Domain: p.CookieDomain,
396+ Domain: cookieDomain,
395397 HttpOnly: p.CookieHTTPOnly,
396398 Secure: p.CookieSecure,
397399 Expires: now.Add(expiration),
oauthproxy_test.go+5−5
@@ -1405,10 +1405,10 @@ func TestAjaxForbiddendRequest(t *testing.T) {
14051405func TestClearSplitCookie(t *testing.T) {
14061406 opts := NewOptions()
14071407 opts.CookieName = "oauth2"
1408− opts.CookieDomain = "abc"
1408+ opts.CookieDomains = []string{"abc"}
14091409 store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
14101410 assert.Equal(t, err, nil)
1411− p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
1411+ p := OAuthProxy{CookieName: opts.CookieName, CookieDomains: opts.CookieDomains, sessionStore: store}
14121412 var rw = httptest.NewRecorder()
14131413 req := httptest.NewRequest("get", "/", nil)
14141414
@@ -1434,10 +1434,10 @@ func TestClearSplitCookie(t *testing.T) {
14341434func TestClearSingleCookie(t *testing.T) {
14351435 opts := NewOptions()
14361436 opts.CookieName = "oauth2"
1437− opts.CookieDomain = "abc"
1437+ opts.CookieDomains = []string{"abc"}
14381438 store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
14391439 assert.Equal(t, err, nil)
1440− p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
1440+ p := OAuthProxy{CookieName: opts.CookieName, CookieDomains: opts.CookieDomains, sessionStore: store}
14411441 var rw = httptest.NewRecorder()
14421442 req := httptest.NewRequest("get", "/", nil)
14431443
@@ -1530,7 +1530,7 @@ func TestGetJwtSession(t *testing.T) {
15301530}
15311531
15321532func TestFindJwtBearerToken(t *testing.T) {
1533− p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
1533+ p := OAuthProxy{CookieName: "oauth2", CookieDomains: []string{"abc"}}
15341534 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
15351535
15361536 validToken := "eyJfoobar.eyJfoobar.12345asdf"
options.go+7
@@ -11,6 +11,7 @@ import (
1111 "net/url"
1212 "os"
1313 "regexp"
14+ "sort"
1415 "strings"
1516 "time"
1617
@@ -402,6 +403,12 @@ func (o *Options) Validate() error {
402403 msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.CookieSameSite))
403404 }
404405
406+ // Sort cookie domains by length, so that we try longer (and more specific)
407+ // domains first
408+ sort.Slice(o.CookieDomains, func(i, j int) bool {
409+ return len(o.CookieDomains[i]) > len(o.CookieDomains[j])
410+ })
411+
405412 msgs = parseSignatureKey(o, msgs)
406413 msgs = validateCookieName(o, msgs)
407414 msgs = setupLogger(o, msgs)
pkg/apis/options/cookie.go+1−1
@@ -6,7 +6,7 @@ import "time"
66type CookieOptions struct {
77 CookieName string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
88 CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
9− CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
9+ CookieDomains []string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
1010 CookiePath string `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"`
1111 CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
1212 CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
pkg/cookies/cookies.go+33−1
@@ -39,7 +39,39 @@ func MakeCookie(req *http.Request, name string, value string, path string, domai
3939// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
4040// value and creation time
4141func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
42− return MakeCookie(req, name, value, opts.CookiePath, opts.CookieDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
42+ domain := GetCookieDomain(req, opts.CookieDomains)
43+
44+ if domain != "" {
45+ return MakeCookie(req, name, value, opts.CookiePath, domain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
46+ }
47+ // If nothing matches, create the cookie with the shortest domain
48+ logger.Printf("Warning: request host %q did not match any of the specific cookie domains of %q", GetRequestHost(req), strings.Join(opts.CookieDomains, ","))
49+ defaultDomain := ""
50+ if len(opts.CookieDomains) > 0 {
51+ defaultDomain = opts.CookieDomains[len(opts.CookieDomains)-1]
52+ }
53+ return MakeCookie(req, name, value, opts.CookiePath, defaultDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
54+}
55+
56+// GetCookieDomain returns the correct cookie domain given a list of domains
57+// by checking the X-Fowarded-Host and host header of an an http request
58+func GetCookieDomain(req *http.Request, cookieDomains []string) string {
59+ host := GetRequestHost(req)
60+ for _, domain := range cookieDomains {
61+ if strings.HasSuffix(host, domain) {
62+ return domain
63+ }
64+ }
65+ return ""
66+}
67+
68+// GetRequestHost return the request host header or X-Forwarded-Host if present
69+func GetRequestHost(req *http.Request) string {
70+ host := req.Header.Get("X-Forwarded-Host")
71+ if host == "" {
72+ host = req.Host
73+ }
74+ return host
4375}
4476
4577// Parse a valid http.SameSite value from a user supplied string for use of making cookies.
pkg/sessions/session_store_test.go+6−2
@@ -63,7 +63,11 @@ var _ = Describe("NewSessionStore", func() {
6363
6464 It("have the correct domain set", func() {
6565 for _, cookie := range cookies {
66− Expect(cookie.Domain).To(Equal(cookieOpts.CookieDomain))
66+ specifiedDomain := ""
67+ if len(cookieOpts.CookieDomains) > 0 {
68+ specifiedDomain = cookieOpts.CookieDomains[0]
69+ }
70+ Expect(cookie.Domain).To(Equal(specifiedDomain))
6771 }
6872 })
6973
@@ -343,7 +347,7 @@ var _ = Describe("NewSessionStore", func() {
343347 CookieRefresh: time.Duration(2) * time.Hour,
344348 CookieSecure: false,
345349 CookieHTTPOnly: false,
346− CookieDomain: "example.com",
350+ CookieDomains: []string{"example.com"},
347351 CookieSameSite: "strict",
348352 }
349353