From 72bb1ac7ccb66fe1b6d200517fb55d87eefcdc3c Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Mon, 4 May 2020 16:26:00 +0100
Subject: [PATCH] Set SameSite policy on CSRF cookies

---
 server/http.go                                |   5 +-
 vendor/github.com/gorilla/csrf/README.md      | 125 +++++++++++++++++-
 .../github.com/gorilla/csrf/context_legacy.go |  28 ----
 vendor/github.com/gorilla/csrf/csrf.go        |  48 ++++++-
 vendor/github.com/gorilla/csrf/go.mod         |   5 +-
 vendor/github.com/gorilla/csrf/go.sum         |   6 +
 vendor/github.com/gorilla/csrf/options.go     |  45 ++++++-
 vendor/github.com/gorilla/csrf/store.go       |   4 +
 .../github.com/gorilla/csrf/store_legacy.go   |  86 ++++++++++++
 vendor/vendor.json                            |   6 +-
 10 files changed, 310 insertions(+), 48 deletions(-)
 delete mode 100644 vendor/github.com/gorilla/csrf/context_legacy.go
 create mode 100644 vendor/github.com/gorilla/csrf/go.sum
 create mode 100644 vendor/github.com/gorilla/csrf/store_legacy.go

diff --git a/server/http.go b/server/http.go
index 30a0c14..6f0a033 100644
--- a/server/http.go
+++ b/server/http.go
@@ -156,7 +156,10 @@ func New(loginService *LoginService, authClient authclient.Client, config *Confi
 
 	apph := httputil.WithDynamicHeaders(loginh, contentSecurityPolicy)
 	if config.CSRFSecret != "" {
-		apph = csrf.Protect([]byte(config.CSRFSecret))(apph)
+		apph = csrf.Protect(
+			[]byte(config.CSRFSecret),
+			csrf.SameSite(csrf.SameSiteStrictMode),
+		)(apph)
 	}
 
 	// Add CORS headers on the main IDP endpoints.
diff --git a/vendor/github.com/gorilla/csrf/README.md b/vendor/github.com/gorilla/csrf/README.md
index 86aae4a..3c7b533 100644
--- a/vendor/github.com/gorilla/csrf/README.md
+++ b/vendor/github.com/gorilla/csrf/README.md
@@ -1,6 +1,9 @@
 # gorilla/csrf
 
-[![GoDoc](https://godoc.org/github.com/gorilla/csrf?status.svg)](https://godoc.org/github.com/gorilla/csrf) [![Build Status](https://travis-ci.org/gorilla/csrf.svg?branch=master)](https://travis-ci.org/gorilla/csrf) [![Sourcegraph](https://sourcegraph.com/github.com/gorilla/csrf/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/csrf?badge)
+[![GoDoc](https://godoc.org/github.com/gorilla/csrf?status.svg)](https://godoc.org/github.com/gorilla/csrf)
+[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/csrf/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/csrf?badge)
+[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
+[![CircleCI](https://circleci.com/gh/gorilla/csrf.svg?style=svg)](https://circleci.com/gh/gorilla/csrf)
 
 gorilla/csrf is a HTTP middleware library that provides [cross-site request
 forgery](http://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/) (CSRF)
@@ -39,6 +42,7 @@ go get github.com/gorilla/csrf
 - [HTML Forms](#html-forms)
 - [JavaScript Apps](#javascript-applications)
 - [Google App Engine](#google-app-engine)
+- [Setting SameSite](#setting-samesite)
 - [Setting Options](#setting-options)
 
 gorilla/csrf is easy to use: add the middleware to your router with
@@ -117,7 +121,15 @@ body.
 ### JavaScript Applications
 
 This approach is useful if you're using a front-end JavaScript framework like
-React, Ember or Angular, or are providing a JSON API.
+React, Ember or Angular, and are providing a JSON API. Specifically, we need
+to provide a way for our front-end fetch/AJAX calls to pass the token on each
+fetch (AJAX/XMLHttpRequest) request. We achieve this by:
+
+- Parsing the token from the `<input>` field generated by the
+  `csrf.TemplateField(r)` helper, or passing it back in a response header.
+- Sending this token back on every request
+- Ensuring our cookie is attached to the request so that the form/header
+  value can be compared to the cookie value.
 
 We'll also look at applying selective CSRF protection using
 [gorilla/mux's](https://www.gorillatoolkit.org/pkg/mux) sub-routers,
@@ -133,12 +145,13 @@ import (
 
 func main() {
     r := mux.NewRouter()
+    csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
 
     api := r.PathPrefix("/api").Subrouter()
+    api.Use(csrfMiddleware)
     api.HandleFunc("/user/{id}", GetUser).Methods("GET")
 
-    http.ListenAndServe(":8000",
-        csrf.Protect([]byte("32-byte-long-auth-key"))(r))
+    http.ListenAndServe(":8000", r)
 }
 
 func GetUser(w http.ResponseWriter, r *http.Request) {
@@ -159,11 +172,82 @@ func GetUser(w http.ResponseWriter, r *http.Request) {
 }
 ```
 
+In our JavaScript application, we should read the token from the response
+headers and pass it in a request header for all requests. Here's what that
+looks like when using [Axios](https://github.com/axios/axios), a popular
+JavaScript HTTP client library:
+
+```js
+// You can alternatively parse the response header for the X-CSRF-Token, and
+// store that instead, if you followed the steps above to write the token to a
+// response header.
+let csrfToken = document.getElementsByName("gorilla.csrf.Token")[0].value
+
+// via https://github.com/axios/axios#creating-an-instance
+const instance = axios.create({
+  baseURL: "https://example.com/api/",
+  timeout: 1000,
+  headers: { "X-CSRF-Token": csrfToken }
+})
+
+// Now, any HTTP request you make will include the csrfToken from the page,
+// provided you update the csrfToken variable for each render.
+try {
+  let resp = await instance.post(endpoint, formData)
+  // Do something with resp
+} catch (err) {
+  // Handle the exception
+}
+```
+
+If you plan to host your JavaScript application on another domain, you can use the Trusted Origins
+feature to allow the host of your JavaScript application to make requests to your Go application. Observe the example below:
+
+
+```go
+package main
+
+import (
+    "github.com/gorilla/csrf"
+    "github.com/gorilla/mux"
+)
+
+func main() {
+    r := mux.NewRouter()
+    csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"), csrf.TrustedOrigin([]string{"ui.domain.com"}))
+
+    api := r.PathPrefix("/api").Subrouter()
+    api.Use(csrfMiddleware)
+    api.HandleFunc("/user/{id}", GetUser).Methods("GET")
+
+    http.ListenAndServe(":8000", r)
+}
+
+func GetUser(w http.ResponseWriter, r *http.Request) {
+    // Authenticate the request, get the id from the route params,
+    // and fetch the user from the DB, etc.
+
+    // Get the token and pass it in the CSRF header. Our JSON-speaking client
+    // or JavaScript framework can now read the header and return the token in
+    // in its own "X-CSRF-Token" request header on the subsequent POST.
+    w.Header().Set("X-CSRF-Token", csrf.Token(r))
+    b, err := json.Marshal(user)
+    if err != nil {
+        http.Error(w, err.Error(), 500)
+        return
+    }
+
+    w.Write(b)
+}
+```
+
+On the example above, you're authorizing requests from `ui.domain.com` to make valid CSRF requests to your application, so you can have your API server on another domain without problems.
+
 ### Google App Engine
 
 If you're using [Google App
 Engine](https://cloud.google.com/appengine/docs/go/how-requests-are-handled#Go_Requests_and_HTTP),
-which doesn't allow you to hook into the default `http.ServeMux` directly,
+(first-generation) which doesn't allow you to hook into the default `http.ServeMux` directly,
 you can still use gorilla/csrf (and gorilla/mux):
 
 ```go
@@ -180,6 +264,34 @@ func init() {
 }
 ```
 
+Note: You can ignore this if you're using the
+[second-generation](https://cloud.google.com/appengine/docs/go/) Go runtime
+on App Engine (Go 1.11 and above).
+
+### Setting SameSite
+
+Go 1.11 introduced the option to set the SameSite attribute in cookies. This is
+valuable if a developer wants to instruct a browser to not include cookies during
+a cross site request. SameSiteStrictMode prevents all cross site requests from including
+the cookie. SameSiteLaxMode prevents CSRF prone requests (POST) from including the cookie
+but allows the cookie to be included in GET requests to support external linking.
+
+```go
+func main() {
+    CSRF := csrf.Protect(
+      []byte("a-32-byte-long-key-goes-here"),
+      // instruct the browser to never send cookies during cross site requests
+      csrf.SameSite(csrf.SameSiteStrictMode),
+    )
+
+    r := mux.NewRouter()
+    r.HandleFunc("/signup", GetSignupForm)
+    r.HandleFunc("/signup/post", PostSignupForm)
+
+    http.ListenAndServe(":8000", CSRF(r))
+}
+```
+
 ### Setting Options
 
 What about providing your own error handler and changing the HTTP header the
@@ -227,6 +339,9 @@ Getting CSRF protection right is important, so here's some background:
 - Cookies are authenticated and based on the [securecookie](https://github.com/gorilla/securecookie)
   library. They're also Secure (issued over HTTPS only) and are HttpOnly
   by default, because sane defaults are important.
+- Cookie SameSite attribute (prevents cookies from being sent by a browser
+  during cross site requests) are not set by default to maintain backwards compatibility
+  for legacy systems. The SameSite attribute can be set with the SameSite option.
 - Go's `crypto/rand` library is used to generate the 32 byte (256 bit) tokens
   and the one-time-pad used for masking them.
 
diff --git a/vendor/github.com/gorilla/csrf/context_legacy.go b/vendor/github.com/gorilla/csrf/context_legacy.go
deleted file mode 100644
index f88c9eb..0000000
--- a/vendor/github.com/gorilla/csrf/context_legacy.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// +build !go1.7
-
-package csrf
-
-import (
-	"net/http"
-
-	"github.com/gorilla/context"
-
-	"github.com/pkg/errors"
-)
-
-func contextGet(r *http.Request, key string) (interface{}, error) {
-	if val, ok := context.GetOk(r, key); ok {
-		return val, nil
-	}
-
-	return nil, errors.Errorf("no value exists in the context for key %q", key)
-}
-
-func contextSave(r *http.Request, key string, val interface{}) *http.Request {
-	context.Set(r, key, val)
-	return r
-}
-
-func contextClear(r *http.Request) {
-	context.Clear(r)
-}
diff --git a/vendor/github.com/gorilla/csrf/csrf.go b/vendor/github.com/gorilla/csrf/csrf.go
index cc7878f..f21e0a2 100644
--- a/vendor/github.com/gorilla/csrf/csrf.go
+++ b/vendor/github.com/gorilla/csrf/csrf.go
@@ -52,6 +52,26 @@ var (
 	ErrBadToken = errors.New("CSRF token invalid")
 )
 
+// SameSiteMode allows a server to define a cookie attribute making it impossible for
+// the browser to send this cookie along with cross-site requests. The main
+// goal is to mitigate the risk of cross-origin information leakage, and provide
+// some protection against cross-site request forgery attacks.
+//
+// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
+type SameSiteMode int
+
+// SameSite options
+const (
+	// SameSiteDefaultMode sets the `SameSite` cookie attribute, which is
+	// invalid in some older browsers due to changes in the SameSite spec. These
+	// browsers will not send the cookie to the server.
+	// csrf uses SameSiteLaxMode (SameSite=Lax) as the default as of v1.7.0+
+	SameSiteDefaultMode SameSiteMode = iota + 1
+	SameSiteLaxMode
+	SameSiteStrictMode
+	SameSiteNoneMode
+)
+
 type csrf struct {
 	h    http.Handler
 	sc   *securecookie.SecureCookie
@@ -66,12 +86,14 @@ type options struct {
 	Path   string
 	// Note that the function and field names match the case of the associated
 	// http.Cookie field instead of the "correct" HTTPOnly name that golint suggests.
-	HttpOnly      bool
-	Secure        bool
-	RequestHeader string
-	FieldName     string
-	ErrorHandler  http.Handler
-	CookieName    string
+	HttpOnly       bool
+	Secure         bool
+	SameSite       SameSiteMode
+	RequestHeader  string
+	FieldName      string
+	ErrorHandler   http.Handler
+	CookieName     string
+	TrustedOrigins []string
 }
 
 // Protect is HTTP middleware that provides Cross-Site Request Forgery
@@ -165,6 +187,7 @@ func Protect(authKey []byte, opts ...Option) func(http.Handler) http.Handler {
 				maxAge:   cs.opts.MaxAge,
 				secure:   cs.opts.Secure,
 				httpOnly: cs.opts.HttpOnly,
+				sameSite: cs.opts.SameSite,
 				path:     cs.opts.Path,
 				domain:   cs.opts.Domain,
 				sc:       cs.sc,
@@ -233,7 +256,18 @@ func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 				return
 			}
 
-			if sameOrigin(r.URL, referer) == false {
+			valid := sameOrigin(r.URL, referer)
+
+			if !valid {
+				for _, trustedOrigin := range cs.opts.TrustedOrigins {
+					if referer.Host == trustedOrigin {
+						valid = true
+						break
+					}
+				}
+			}
+
+			if valid == false {
 				r = envError(r, ErrBadReferer)
 				cs.opts.ErrorHandler.ServeHTTP(w, r)
 				return
diff --git a/vendor/github.com/gorilla/csrf/go.mod b/vendor/github.com/gorilla/csrf/go.mod
index 2d2ce4d..23a5c6e 100644
--- a/vendor/github.com/gorilla/csrf/go.mod
+++ b/vendor/github.com/gorilla/csrf/go.mod
@@ -1,7 +1,8 @@
 module github.com/gorilla/csrf
 
 require (
-	github.com/gorilla/context v1.1.1
 	github.com/gorilla/securecookie v1.1.1
-	github.com/pkg/errors v0.8.0
+	github.com/pkg/errors v0.9.1
 )
+
+go 1.13
diff --git a/vendor/github.com/gorilla/csrf/go.sum b/vendor/github.com/gorilla/csrf/go.sum
new file mode 100644
index 0000000..ff4965c
--- /dev/null
+++ b/vendor/github.com/gorilla/csrf/go.sum
@@ -0,0 +1,6 @@
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/vendor/github.com/gorilla/csrf/options.go b/vendor/github.com/gorilla/csrf/options.go
index b50ebd4..c61d301 100644
--- a/vendor/github.com/gorilla/csrf/options.go
+++ b/vendor/github.com/gorilla/csrf/options.go
@@ -1,12 +1,15 @@
 package csrf
 
-import "net/http"
+import (
+	"net/http"
+)
 
 // Option describes a functional option for configuring the CSRF handler.
 type Option func(*csrf)
 
 // MaxAge sets the maximum age (in seconds) of a CSRF token's underlying cookie.
-// Defaults to 12 hours.
+// Defaults to 12 hours. Call csrf.MaxAge(0) to explicitly set session-only
+// cookies.
 func MaxAge(age int) Option {
 	return func(cs *csrf) {
 		cs.opts.MaxAge = age
@@ -58,6 +61,26 @@ func HttpOnly(h bool) Option {
 	}
 }
 
+// SameSite sets the cookie SameSite attribute. Defaults to blank to maintain
+// backwards compatibility, however, Strict is recommended.
+//
+// SameSite(SameSiteStrictMode) will prevent the cookie from being sent by the
+// browser to the target site in all cross-site browsing context, even when
+// following a regular link (GET request).
+//
+// SameSite(SameSiteLaxMode) provides a reasonable balance between security and
+// usability for websites that want to maintain user's logged-in session after
+// the user arrives from an external link. The session cookie would be allowed
+// when following a regular link from an external website while blocking it in
+// CSRF-prone request methods (e.g. POST).
+//
+// This option is only available for go 1.11+.
+func SameSite(s SameSiteMode) Option {
+	return func(cs *csrf) {
+		cs.opts.SameSite = s
+	}
+}
+
 // ErrorHandler allows you to change the handler called when CSRF request
 // processing encounters an invalid token or request. A typical use would be to
 // provide a handler that returns a static HTML file with a HTTP 403 status. By
@@ -97,6 +120,17 @@ func CookieName(name string) Option {
 	}
 }
 
+// TrustedOrigins configures a set of origins (Referers) that are considered as trusted.
+// This will allow cross-domain CSRF use-cases - e.g. where the front-end is served
+// from a different domain than the API server - to correctly pass a CSRF check.
+//
+// You should only provide origins you own or have full control over.
+func TrustedOrigins(origins []string) Option {
+	return func(cs *csrf) {
+		cs.opts.TrustedOrigins = origins
+	}
+}
+
 // setStore sets the store used by the CSRF middleware.
 // Note: this is private (for now) to allow for internal API changes.
 func setStore(s store) Option {
@@ -118,6 +152,13 @@ func parseOptions(h http.Handler, opts ...Option) *csrf {
 	cs.opts.Secure = true
 	cs.opts.HttpOnly = true
 
+	// Set SameSite=Lax by default, allowing the CSRF cookie to only be sent on
+	// top-level navigations.
+	cs.opts.SameSite = SameSiteLaxMode
+
+	// Default; only override this if the package user explicitly calls MaxAge(0)
+	cs.opts.MaxAge = defaultAge
+
 	// Range over each options function and apply it
 	// to our csrf type to configure it. Options functions are
 	// applied in order, with any conflicting options overriding
diff --git a/vendor/github.com/gorilla/csrf/store.go b/vendor/github.com/gorilla/csrf/store.go
index 39f47ad..f7997fc 100644
--- a/vendor/github.com/gorilla/csrf/store.go
+++ b/vendor/github.com/gorilla/csrf/store.go
@@ -1,3 +1,5 @@
+// +build go1.11
+
 package csrf
 
 import (
@@ -28,6 +30,7 @@ type cookieStore struct {
 	path     string
 	domain   string
 	sc       *securecookie.SecureCookie
+	sameSite SameSiteMode
 }
 
 // Get retrieves a CSRF token from the session cookie. It returns an empty token
@@ -63,6 +66,7 @@ func (cs *cookieStore) Save(token []byte, w http.ResponseWriter) error {
 		MaxAge:   cs.maxAge,
 		HttpOnly: cs.httpOnly,
 		Secure:   cs.secure,
+		SameSite: http.SameSite(cs.sameSite),
 		Path:     cs.path,
 		Domain:   cs.domain,
 	}
diff --git a/vendor/github.com/gorilla/csrf/store_legacy.go b/vendor/github.com/gorilla/csrf/store_legacy.go
new file mode 100644
index 0000000..b211164
--- /dev/null
+++ b/vendor/github.com/gorilla/csrf/store_legacy.go
@@ -0,0 +1,86 @@
+// +build !go1.11
+// file for compatibility with go versions prior to 1.11
+
+package csrf
+
+import (
+	"net/http"
+	"time"
+
+	"github.com/gorilla/securecookie"
+)
+
+// store represents the session storage used for CSRF tokens.
+type store interface {
+	// Get returns the real CSRF token from the store.
+	Get(*http.Request) ([]byte, error)
+	// Save stores the real CSRF token in the store and writes a
+	// cookie to the http.ResponseWriter.
+	// For non-cookie stores, the cookie should contain a unique (256 bit) ID
+	// or key that references the token in the backend store.
+	// csrf.GenerateRandomBytes is a helper function for generating secure IDs.
+	Save(token []byte, w http.ResponseWriter) error
+}
+
+// cookieStore is a signed cookie session store for CSRF tokens.
+type cookieStore struct {
+	name     string
+	maxAge   int
+	secure   bool
+	httpOnly bool
+	path     string
+	domain   string
+	sc       *securecookie.SecureCookie
+	sameSite SameSiteMode
+}
+
+// Get retrieves a CSRF token from the session cookie. It returns an empty token
+// if decoding fails (e.g. HMAC validation fails or the named cookie doesn't exist).
+func (cs *cookieStore) Get(r *http.Request) ([]byte, error) {
+	// Retrieve the cookie from the request
+	cookie, err := r.Cookie(cs.name)
+	if err != nil {
+		return nil, err
+	}
+
+	token := make([]byte, tokenLength)
+	// Decode the HMAC authenticated cookie.
+	err = cs.sc.Decode(cs.name, cookie.Value, &token)
+	if err != nil {
+		return nil, err
+	}
+
+	return token, nil
+}
+
+// Save stores the CSRF token in the session cookie.
+func (cs *cookieStore) Save(token []byte, w http.ResponseWriter) error {
+	// Generate an encoded cookie value with the CSRF token.
+	encoded, err := cs.sc.Encode(cs.name, token)
+	if err != nil {
+		return err
+	}
+
+	cookie := &http.Cookie{
+		Name:     cs.name,
+		Value:    encoded,
+		MaxAge:   cs.maxAge,
+		HttpOnly: cs.httpOnly,
+		Secure:   cs.secure,
+		Path:     cs.path,
+		Domain:   cs.domain,
+	}
+
+	// Set the Expires field on the cookie based on the MaxAge
+	// If MaxAge <= 0, we don't set the Expires attribute, making the cookie
+	// session-only.
+	if cs.maxAge > 0 {
+		cookie.Expires = time.Now().Add(
+			time.Duration(cs.maxAge) * time.Second)
+	}
+
+	// Write the authenticated cookie to the response.
+	http.SetCookie(w, cookie)
+
+	return nil
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index ee40850..4aa78a0 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -111,10 +111,10 @@
 			"revisionTime": "2018-10-12T15:35:48Z"
 		},
 		{
-			"checksumSHA1": "uzOh/6ll8f2HnCNHDWRenwQ/Owo=",
+			"checksumSHA1": "3CtHe9LMWm74S8eocCfYx0EXj/U=",
 			"path": "github.com/gorilla/csrf",
-			"revision": "f903b4ea4d6056635620f6f39e930528b97f9a55",
-			"revisionTime": "2018-10-12T15:34:37Z"
+			"revision": "79c60d0e4fcf1fbc9653c1cb13d28e82248cf43c",
+			"revisionTime": "2020-04-26T17:13:33Z"
 		},
 		{
 			"checksumSHA1": "22kXObb09lweSbdIjPZGeLBjnkg=",
-- 
GitLab