git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #339 from clubhouse/pgroudas/add-samesite-cookie-options
ec72ee8Joel Speed
CHANGELOG.md+2
@@ -5,6 +5,8 @@
5## Breaking Changes
5## Breaking Changes
6
6
7## Changes since v4.1.0
7## Changes since v4.1.0
8+
9+- [#339](https://github.com/pusher/oauth2_proxy/pull/339) Add configuration for cookie 'SameSite' value. (@pgroudas)
8- [#347](https://github.com/pusher/oauth2_proxy/pull/347) Update keycloak provider configuration documentation
10- [#347](https://github.com/pusher/oauth2_proxy/pull/347) Update keycloak provider configuration documentation
9- [#325](https://github.com/pusher/oauth2_proxy/pull/325) dist.sh: use sha256sum (@syscll)
11- [#325](https://github.com/pusher/oauth2_proxy/pull/325) dist.sh: use sha256sum (@syscll)
10- [#179](https://github.com/pusher/oauth2_proxy/pull/179) Add Nextcloud provider (@Ramblurr)
12- [#179](https://github.com/pusher/oauth2_proxy/pull/179) Add Nextcloud provider (@Ramblurr)
contrib/oauth2_proxy_autocomplete.sh+1−1
@@ -20,7 +20,7 @@ _oauth2_proxy() {
20 COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov digitalocean" -- ${cur}) )
20 COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov digitalocean" -- ${cur}) )
21 return 0
21 return 0
22 ;;
22 ;;
23− -@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|gitlab-group|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|redist-sentinel-master-name|redist-sentinel-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url))
23+ -@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|gitlab-group|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url))
24 return 0
24 return 0
25 ;;
25 ;;
26 esac
26 esac
docs/configuration/configuration.md+1
@@ -38,6 +38,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
38| `-cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable | |
38| `-cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable | |
39| `-cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
39| `-cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
40| `-cookie-secure` | bool | set secure (HTTPS) cookie flag | true |
40| `-cookie-secure` | bool | set secure (HTTPS) cookie flag | true |
41+| `-cookie-samesite` | string | set SameSite cookie attribute (ie: `"lax"`, `"strict"`, `"none"`, or `""`). | `""` |
41| `-custom-templates-dir` | string | path to custom html templates | |
42| `-custom-templates-dir` | string | path to custom html templates | |
42| `-display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true |
43| `-display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true |
43| `-email-domain` | string | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | |
44| `-email-domain` | string | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | |
main.go+1
@@ -86,6 +86,7 @@ func main() {
86 flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
86 flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
87 flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
87 flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
88 flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
88 flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
89+ flagSet.String("cookie-samesite", "", "set SameSite cookie attribute (ie: \"lax\", \"strict\", \"none\", or \"\"). ")
89
90
90 flagSet.String("session-store-type", "cookie", "the session storage provider to use")
91 flagSet.String("session-store-type", "cookie", "the session storage provider to use")
91 flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
92 flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
oauthproxy.go+6−2
@@ -20,6 +20,7 @@ import (
20 "github.com/coreos/go-oidc"
20 "github.com/coreos/go-oidc"
21 "github.com/mbland/hmacauth"
21 "github.com/mbland/hmacauth"
22 sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
22 sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
23+ "github.com/pusher/oauth2_proxy/pkg/cookies"
23 "github.com/pusher/oauth2_proxy/pkg/encryption"
24 "github.com/pusher/oauth2_proxy/pkg/encryption"
24 "github.com/pusher/oauth2_proxy/pkg/logger"
25 "github.com/pusher/oauth2_proxy/pkg/logger"
25 "github.com/pusher/oauth2_proxy/providers"
26 "github.com/pusher/oauth2_proxy/providers"
@@ -68,6 +69,7 @@ type OAuthProxy struct {
68 CookieHTTPOnly bool
69 CookieHTTPOnly bool
69 CookieExpire time.Duration
70 CookieExpire time.Duration
70 CookieRefresh time.Duration
71 CookieRefresh time.Duration
72+ CookieSameSite string
71 Validator func(string) bool
73 Validator func(string) bool
72
74
73 RobotsPath string
75 RobotsPath string
@@ -195,7 +197,7 @@ func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.Hma
195 }
197 }
196}
198}
197
199
198−// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
200+// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
199func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
201func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
200 serveMux := http.NewServeMux()
202 serveMux := http.NewServeMux()
201 var auth hmacauth.HmacAuth
203 var auth hmacauth.HmacAuth
@@ -260,7 +262,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
260 refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
262 refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
261 }
263 }
262
264
263− logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
265+ 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)
264
266
265 return &OAuthProxy{
267 return &OAuthProxy{
266 CookieName: opts.CookieName,
268 CookieName: opts.CookieName,
@@ -272,6 +274,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
272 CookieHTTPOnly: opts.CookieHTTPOnly,
274 CookieHTTPOnly: opts.CookieHTTPOnly,
273 CookieExpire: opts.CookieExpire,
275 CookieExpire: opts.CookieExpire,
274 CookieRefresh: opts.CookieRefresh,
276 CookieRefresh: opts.CookieRefresh,
277+ CookieSameSite: opts.CookieSameSite,
275 Validator: validator,
278 Validator: validator,
276
279
277 RobotsPath: "/robots.txt",
280 RobotsPath: "/robots.txt",
@@ -380,6 +383,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
380 HttpOnly: p.CookieHTTPOnly,
383 HttpOnly: p.CookieHTTPOnly,
381 Secure: p.CookieSecure,
384 Secure: p.CookieSecure,
382 Expires: now.Add(expiration),
385 Expires: now.Add(expiration),
386+ SameSite: cookies.ParseSameSite(p.CookieSameSite),
383 }
387 }
384}
388}
385
389
options.go+6
@@ -372,6 +372,12 @@ func (o *Options) Validate() error {
372 }
372 }
373 }
373 }
374
374
375+ switch o.CookieSameSite {
376+ case "", "none", "lax", "strict":
377+ default:
378+ msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.CookieSameSite))
379+ }
380+
375 msgs = parseSignatureKey(o, msgs)
381 msgs = parseSignatureKey(o, msgs)
376 msgs = validateCookieName(o, msgs)
382 msgs = validateCookieName(o, msgs)
377 msgs = setupLogger(o, msgs)
383 msgs = setupLogger(o, msgs)
pkg/apis/options/cookie.go+1
@@ -12,4 +12,5 @@ type CookieOptions struct {
12 CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
12 CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
13 CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
13 CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
14 CookieHTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
14 CookieHTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
15+ CookieSameSite string `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"`
15}
16}
pkg/cookies/cookies.go+21−3
@@ -1,6 +1,7 @@
1package cookies
1package cookies
2
2
3import (
3import (
4+ "fmt"
4 "net"
5 "net"
5 "net/http"
6 "net/http"
6 "strings"
7 "strings"
@@ -12,7 +13,7 @@ import (
12
13
13// MakeCookie constructs a cookie from the given parameters,
14// MakeCookie constructs a cookie from the given parameters,
14// discovering the domain from the request if not specified.
15// discovering the domain from the request if not specified.
15−func MakeCookie(req *http.Request, name string, value string, path string, domain string, httpOnly bool, secure bool, expiration time.Duration, now time.Time) *http.Cookie {
16+func MakeCookie(req *http.Request, name string, value string, path string, domain string, httpOnly bool, secure bool, expiration time.Duration, now time.Time, sameSite http.SameSite) *http.Cookie {
16 if domain != "" {
17 if domain != "" {
17 host := req.Host
18 host := req.Host
18 if h, _, err := net.SplitHostPort(host); err == nil {
19 if h, _, err := net.SplitHostPort(host); err == nil {
@@ -31,11 +32,28 @@ func MakeCookie(req *http.Request, name string, value string, path string, domai
31 HttpOnly: httpOnly,
32 HttpOnly: httpOnly,
32 Secure: secure,
33 Secure: secure,
33 Expires: now.Add(expiration),
34 Expires: now.Add(expiration),
35+ SameSite: sameSite,
34 }
36 }
35}
37}
36
38
37−// MakeCookieFromOptions constructs a cookie based on the givemn *options.CookieOptions,
39+// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
38// value and creation time
40// value and creation time
39func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
41func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
40− return MakeCookie(req, name, value, opts.CookiePath, opts.CookieDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now)
42+ return MakeCookie(req, name, value, opts.CookiePath, opts.CookieDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
43+}
44+
45+// Parse a valid http.SameSite value from a user supplied string for use of making cookies.
46+func ParseSameSite(v string) http.SameSite {
47+ switch v {
48+ case "lax":
49+ return http.SameSiteLaxMode
50+ case "strict":
51+ return http.SameSiteStrictMode
52+ case "none":
53+ return http.SameSiteNoneMode
54+ case "":
55+ return http.SameSiteDefaultMode
56+ default:
57+ panic(fmt.Sprintf("Invalid value for SameSite: %s", v))
58+ }
41}
59}
pkg/sessions/session_store_test.go+10−2
@@ -15,7 +15,7 @@ import (
15 . "github.com/onsi/gomega"
15 . "github.com/onsi/gomega"
16 "github.com/pusher/oauth2_proxy/pkg/apis/options"
16 "github.com/pusher/oauth2_proxy/pkg/apis/options"
17 sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
17 sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
18− "github.com/pusher/oauth2_proxy/pkg/cookies"
18+ cookiesapi "github.com/pusher/oauth2_proxy/pkg/cookies"
19 "github.com/pusher/oauth2_proxy/pkg/encryption"
19 "github.com/pusher/oauth2_proxy/pkg/encryption"
20 "github.com/pusher/oauth2_proxy/pkg/sessions"
20 "github.com/pusher/oauth2_proxy/pkg/sessions"
21 sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
21 sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
@@ -79,6 +79,12 @@ var _ = Describe("NewSessionStore", func() {
79 }
79 }
80 })
80 })
81
81
82+ It("have the correct SameSite set", func() {
83+ for _, cookie := range cookies {
84+ Expect(cookie.SameSite).To(Equal(cookiesapi.ParseSameSite(cookieOpts.CookieSameSite)))
85+ }
86+ })
87+
82 It("have a signature timestamp matching session.CreatedAt", func() {
88 It("have a signature timestamp matching session.CreatedAt", func() {
83 for _, cookie := range cookies {
89 for _, cookie := range cookies {
84 if cookie.Value != "" {
90 if cookie.Value != "" {
@@ -159,7 +165,7 @@ var _ = Describe("NewSessionStore", func() {
159 By("Using a valid cookie with a different providers session encoding")
165 By("Using a valid cookie with a different providers session encoding")
160 broken := "BrokenSessionFromADifferentSessionImplementation"
166 broken := "BrokenSessionFromADifferentSessionImplementation"
161 value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
167 value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
162− cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
168+ cookie := cookiesapi.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
163 request.AddCookie(cookie)
169 request.AddCookie(cookie)
164
170
165 err := ss.Save(response, request, session)
171 err := ss.Save(response, request, session)
@@ -338,6 +344,7 @@ var _ = Describe("NewSessionStore", func() {
338 CookieSecure: false,
344 CookieSecure: false,
339 CookieHTTPOnly: false,
345 CookieHTTPOnly: false,
340 CookieDomain: "example.com",
346 CookieDomain: "example.com",
347+ CookieSameSite: "strict",
341 }
348 }
342
349
343 var err error
350 var err error
@@ -379,6 +386,7 @@ var _ = Describe("NewSessionStore", func() {
379 CookieRefresh: time.Duration(1) * time.Hour,
386 CookieRefresh: time.Duration(1) * time.Hour,
380 CookieSecure: true,
387 CookieSecure: true,
381 CookieHTTPOnly: true,
388 CookieHTTPOnly: true,
389+ CookieSameSite: "",
382 }
390 }
383
391
384 session = &sessionsapi.SessionState{
392 session = &sessionsapi.SessionState{