From df418a2783269e81b19e35ab77cfa3691758003a Mon Sep 17 00:00:00 2001 From: kannappanr <30541348+kannappanr@users.noreply.github.com> Date: Tue, 5 Feb 2019 14:06:52 -0800 Subject: [PATCH] Create Cors handler with permissive configuration (#7186) Create new Cors handler allowing all origins with all standard methods with any header and credentials. Fixes #7181 --- cmd/generic-handlers.go | 30 +------ vendor/github.com/rs/cors/README.md | 20 ++++- vendor/github.com/rs/cors/cors.go | 134 +++++++++++++++------------- vendor/github.com/rs/cors/go.mod | 1 + vendor/github.com/rs/cors/utils.go | 9 +- vendor/vendor.json | 5 +- 6 files changed, 101 insertions(+), 98 deletions(-) create mode 100644 vendor/github.com/rs/cors/go.mod diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go index a7d4960e1..fcc2e4b72 100644 --- a/cmd/generic-handlers.go +++ b/cmd/generic-handlers.go @@ -381,37 +381,9 @@ type resourceHandler struct { handler http.Handler } -// List of default allowable HTTP methods. -var defaultAllowableHTTPMethods = []string{ - http.MethodGet, - http.MethodPut, - http.MethodHead, - http.MethodPost, - http.MethodDelete, - http.MethodOptions, -} - // setCorsHandler handler for CORS (Cross Origin Resource Sharing) func setCorsHandler(h http.Handler) http.Handler { - commonS3Headers := []string{ - "Date", - "ETag", - "Server", - "Connection", - "Accept-Ranges", - "Content-Range", - "Content-Encoding", - "Content-Length", - "Content-Type", - "x-amz-request-id", - } - c := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: defaultAllowableHTTPMethods, - AllowedHeaders: []string{"*"}, - ExposedHeaders: commonS3Headers, - AllowCredentials: true, - }) + c := cors.AllowAll() return c.Handler(h) } diff --git a/vendor/github.com/rs/cors/README.md b/vendor/github.com/rs/cors/README.md index 4bf56724e..ecc83b295 100644 --- a/vendor/github.com/rs/cors/README.md +++ b/vendor/github.com/rs/cors/README.md @@ -49,6 +49,14 @@ The server now runs on `localhost:8080`: {"hello": "world"} +### Allow * With Credentials Security Protection + +This library has been modified to avoid a well known security issue when configured with `AllowedOrigins` to `*` and `AllowCredentials` to `true`. Such setup used to make the library reflects the request `Origin` header value, working around a security protection embedded into the standard that makes clients to refuse such configuration. This behavior has been removed with [#55](https://github.com/rs/cors/issues/55) and [#57](https://github.com/rs/cors/issues/57). + +If you depend on this behavior and understand the implications, you can restore it using the `AllowOriginFunc` with `func(origin string) {return true}`. + +Please refer to [#55](https://github.com/rs/cors/issues/55) for more information about the security implications. + ### More Examples * `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go) @@ -56,6 +64,11 @@ The server now runs on `localhost:8080`: * [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go) * [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go) * [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go) +* [HttpRouter](https://github.com/julienschmidt/httprouter): [examples/httprouter/server.go](https://github.com/rs/cors/blob/master/examples/httprouter/server.go) +* [Gorilla](http://www.gorillatoolkit.org/pkg/mux): [examples/gorilla/server.go](https://github.com/rs/cors/blob/master/examples/gorilla/server.go) +* [Buffalo](https://gobuffalo.io): [examples/buffalo/server.go](https://github.com/rs/cors/blob/master/examples/buffalo/server.go) +* [Gin](https://gin-gonic.github.io/gin): [examples/gin/server.go](https://github.com/rs/cors/blob/master/examples/gin/server.go) +* [Chi](https://github.com/go-chi/chi): [examples/chi/server.go](https://github.com/rs/cors/blob/master/examples/chi/server.go) ## Parameters @@ -63,8 +76,10 @@ Parameters are passed to the middleware thru the `cors.New` method as follow: ```go c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, + AllowedOrigins: []string{"http://foo.com", "http://foo.com:8080"}, AllowCredentials: true, + // Enable Debugging for testing, consider disabling in production + Debug: true, }) // Insert the middleware @@ -72,7 +87,8 @@ handler = c.Handler(handler) ``` * **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. An origin may contain a wildcard (`*`) to replace 0 or more characters (i.e.: `http://*.domain.com`). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin. The default value is `*`. -* **AllowOriginFunc** `func (origin string) bool`: A custom function to validate the origin. It take the origin as argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` is ignored +* **AllowOriginFunc** `func (origin string) bool`: A custom function to validate the origin. It takes the origin as an argument and returns true if allowed, or false otherwise. If this option is set, the content of `AllowedOrigins` is ignored. +* **AllowOriginRequestFunc** `func (r *http.Request origin string) bool`: A custom function to validate the origin. It takes the HTTP Request object and the origin as argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` and `AllowOriginFunc` is ignored * **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`). * **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. * **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification diff --git a/vendor/github.com/rs/cors/cors.go b/vendor/github.com/rs/cors/cors.go index 4bb22d8fc..10019eb89 100644 --- a/vendor/github.com/rs/cors/cors.go +++ b/vendor/github.com/rs/cors/cors.go @@ -5,8 +5,8 @@ as defined by http://www.w3.org/TR/cors/ You can configure it by passing an option struct to cors.New: c := cors.New(cors.Options{ - AllowedOrigins: []string{"foo.com"}, - AllowedMethods: []string{"GET", "POST", "DELETE"}, + AllowedOrigins: []string{"foo.com"}, + AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, AllowCredentials: true, }) @@ -26,9 +26,6 @@ import ( "os" "strconv" "strings" - - "github.com/rs/xhandler" - "golang.org/x/net/context" ) // Options is a configuration container to setup the CORS middleware. @@ -36,7 +33,7 @@ type Options struct { // AllowedOrigins is a list of origins a cross-domain request can be executed from. // If the special "*" value is present in the list, all origins will be allowed. // An origin may contain a wildcard (*) to replace 0 or more characters - // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. + // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty. // Only one wildcard can be used per origin. // Default value is ["*"] AllowedOrigins []string @@ -44,8 +41,12 @@ type Options struct { // as argument and returns true if allowed or false otherwise. If this option is // set, the content of AllowedOrigins is ignored. AllowOriginFunc func(origin string) bool + // AllowOriginFunc is a custom function to validate the origin. It takes the HTTP Request object and the origin as + // argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` + // and `AllowOriginFunc` is ignored. + AllowOriginRequestFunc func(r *http.Request, origin string) bool // AllowedMethods is a list of methods the client is allowed to use with - // cross-domain requests. Default value is simple methods (GET and POST) + // cross-domain requests. Default value is simple methods (HEAD, GET and POST). AllowedMethods []string // AllowedHeaders is list of non simple headers the client is allowed to use with // cross-domain requests. @@ -55,12 +56,12 @@ type Options struct { // ExposedHeaders indicates which headers are safe to expose to the API of a CORS // API specification ExposedHeaders []string - // AllowCredentials indicates whether the request can include user credentials like - // cookies, HTTP authentication or client side SSL certificates. - AllowCredentials bool // MaxAge indicates how long (in seconds) the results of a preflight request // can be cached MaxAge int + // AllowCredentials indicates whether the request can include user credentials like + // cookies, HTTP authentication or client side SSL certificates. + AllowCredentials bool // OptionsPassthrough instructs preflight to let other potential next handlers to // process the OPTIONS method. Turn this on if your application handles OPTIONS. OptionsPassthrough bool @@ -72,35 +73,38 @@ type Options struct { type Cors struct { // Debug logger Log *log.Logger - // Set to true when allowed origins contains a "*" - allowedOriginsAll bool // Normalized list of plain allowed origins allowedOrigins []string // List of allowed origins containing wildcards allowedWOrigins []wildcard // Optional origin validator function allowOriginFunc func(origin string) bool - // Set to true when allowed headers contains a "*" - allowedHeadersAll bool + // Optional origin validator (with request) function + allowOriginRequestFunc func(r *http.Request, origin string) bool // Normalized list of allowed headers allowedHeaders []string // Normalized list of allowed methods allowedMethods []string // Normalized list of exposed headers - exposedHeaders []string + exposedHeaders []string + maxAge int + // Set to true when allowed origins contains a "*" + allowedOriginsAll bool + // Set to true when allowed headers contains a "*" + allowedHeadersAll bool allowCredentials bool - maxAge int optionPassthrough bool } // New creates a new Cors handler with the provided options. func New(options Options) *Cors { c := &Cors{ - exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), - allowOriginFunc: options.AllowOriginFunc, - allowCredentials: options.AllowCredentials, - maxAge: options.MaxAge, - optionPassthrough: options.OptionsPassthrough, + exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), + allowOriginFunc: options.AllowOriginFunc, + allowOriginRequestFunc: options.AllowOriginRequestFunc, + allowCredentials: options.AllowCredentials, + maxAge: options.MaxAge, + optionPassthrough: options.OptionsPassthrough, } if options.Debug { c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags) @@ -112,8 +116,10 @@ func New(options Options) *Cors { // Allowed Origins if len(options.AllowedOrigins) == 0 { - // Default is all origins - c.allowedOriginsAll = true + if options.AllowOriginFunc == nil && options.AllowOriginRequestFunc == nil { + // Default is all origins + c.allowedOriginsAll = true + } } else { c.allowedOrigins = []string{} c.allowedWOrigins = []wildcard{} @@ -128,7 +134,7 @@ func New(options Options) *Cors { break } else if i := strings.IndexByte(origin, '*'); i >= 0 { // Split the origin in two: start and end string without the * - w := wildcard{origin[0:i], origin[i+1 : len(origin)]} + w := wildcard{origin[0:i], origin[i+1:]} c.allowedWOrigins = append(c.allowedWOrigins, w) } else { c.allowedOrigins = append(c.allowedOrigins, origin) @@ -139,7 +145,7 @@ func New(options Options) *Cors { // Allowed Headers if len(options.AllowedHeaders) == 0 { // Use sensible defaults - c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"} + c.allowedHeaders = []string{"Origin", "Accept", "Content-Type", "X-Requested-With"} } else { // Origin is always appended as some browsers will always request for this header at preflight c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey) @@ -155,7 +161,7 @@ func New(options Options) *Cors { // Allowed Methods if len(options.AllowedMethods) == 0 { // Default is spec's "simple" methods - c.allowedMethods = []string{"GET", "POST"} + c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead} } else { c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) } @@ -163,16 +169,34 @@ func New(options Options) *Cors { return c } -// Default creates a new Cors handler with default options +// Default creates a new Cors handler with default options. func Default() *Cors { return New(Options{}) } +// AllowAll create a new Cors handler with permissive configuration allowing all +// origins with all standard methods with any header and credentials. +func AllowAll() *Cors { + return New(Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{ + http.MethodHead, + http.MethodGet, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + }, + AllowedHeaders: []string{"*"}, + AllowCredentials: false, + }) +} + // Handler apply the CORS specification on the request, and add relevant CORS headers // as necessary. func (c *Cors) Handler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { + if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { c.logf("Handler: Preflight request") c.handlePreflight(w, r) // Preflight requests are standalone and should stop the chain as some other @@ -192,32 +216,9 @@ func (c *Cors) Handler(h http.Handler) http.Handler { }) } -// HandlerC is net/context aware handler -func (c *Cors) HandlerC(h xhandler.HandlerC) xhandler.HandlerC { - return xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { - c.logf("Handler: Preflight request") - c.handlePreflight(w, r) - // Preflight requests are standalone and should stop the chain as some other - // middleware may not handle OPTIONS requests correctly. One typical example - // is authentication middleware ; OPTIONS requests won't carry authentication - // headers (see #1) - if c.optionPassthrough { - h.ServeHTTPC(ctx, w, r) - } else { - w.WriteHeader(http.StatusOK) - } - } else { - c.logf("Handler: Actual request") - c.handleActualRequest(w, r) - h.ServeHTTPC(ctx, w, r) - } - }) -} - // HandlerFunc provides Martini compatible handler func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { + if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { c.logf("HandlerFunc: Preflight request") c.handlePreflight(w, r) } else { @@ -228,7 +229,7 @@ func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) { // Negroni compatible interface func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - if r.Method == "OPTIONS" { + if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { c.logf("ServeHTTP: Preflight request") c.handlePreflight(w, r) // Preflight requests are standalone and should stop the chain as some other @@ -252,7 +253,7 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { headers := w.Header() origin := r.Header.Get("Origin") - if r.Method != "OPTIONS" { + if r.Method != http.MethodOptions { c.logf(" Preflight aborted: %s!=OPTIONS", r.Method) return } @@ -267,7 +268,7 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { c.logf(" Preflight aborted: empty origin") return } - if !c.isOriginAllowed(origin) { + if !c.isOriginAllowed(r, origin) { c.logf(" Preflight aborted: origin '%s' not allowed", origin) return } @@ -282,7 +283,11 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) return } - headers.Set("Access-Control-Allow-Origin", origin) + if c.allowedOriginsAll { + headers.Set("Access-Control-Allow-Origin", "*") + } else { + headers.Set("Access-Control-Allow-Origin", origin) + } // Spec says: Since the list of methods can be unbounded, simply returning the method indicated // by Access-Control-Request-Method (if supported) can be enough headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) @@ -306,7 +311,7 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { headers := w.Header() origin := r.Header.Get("Origin") - if r.Method == "OPTIONS" { + if r.Method == http.MethodOptions { c.logf(" Actual request no headers added: method == %s", r.Method) return } @@ -316,7 +321,7 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { c.logf(" Actual request no headers added: missing origin") return } - if !c.isOriginAllowed(origin) { + if !c.isOriginAllowed(r, origin) { c.logf(" Actual request no headers added: origin '%s' not allowed", origin) return } @@ -330,7 +335,11 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { return } - headers.Set("Access-Control-Allow-Origin", origin) + if c.allowedOriginsAll { + headers.Set("Access-Control-Allow-Origin", "*") + } else { + headers.Set("Access-Control-Allow-Origin", origin) + } if len(c.exposedHeaders) > 0 { headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", ")) } @@ -349,7 +358,10 @@ func (c *Cors) logf(format string, a ...interface{}) { // isOriginAllowed checks if a given origin is allowed to perform cross-domain requests // on the endpoint -func (c *Cors) isOriginAllowed(origin string) bool { +func (c *Cors) isOriginAllowed(r *http.Request, origin string) bool { + if c.allowOriginRequestFunc != nil { + return c.allowOriginRequestFunc(r, origin) + } if c.allowOriginFunc != nil { return c.allowOriginFunc(origin) } @@ -378,7 +390,7 @@ func (c *Cors) isMethodAllowed(method string) bool { return false } method = strings.ToUpper(method) - if method == "OPTIONS" { + if method == http.MethodOptions { // Always allow preflight requests return true } diff --git a/vendor/github.com/rs/cors/go.mod b/vendor/github.com/rs/cors/go.mod new file mode 100644 index 000000000..0a4c65210 --- /dev/null +++ b/vendor/github.com/rs/cors/go.mod @@ -0,0 +1 @@ +module github.com/rs/cors diff --git a/vendor/github.com/rs/cors/utils.go b/vendor/github.com/rs/cors/utils.go index c7a0aa060..db83ac3ea 100644 --- a/vendor/github.com/rs/cors/utils.go +++ b/vendor/github.com/rs/cors/utils.go @@ -12,7 +12,7 @@ type wildcard struct { } func (w wildcard) match(s string) bool { - return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) + return len(s) >= len(w.prefix)+len(w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) } // convert converts a list of string using the passed converter function @@ -39,19 +39,20 @@ func parseHeaderList(headerList string) []string { headers := make([]string, 0, t) for i := 0; i < l; i++ { b := headerList[i] - if b >= 'a' && b <= 'z' { + switch { + case b >= 'a' && b <= 'z': if upper { h = append(h, b-toLower) } else { h = append(h, b) } - } else if b >= 'A' && b <= 'Z' { + case b >= 'A' && b <= 'Z': if !upper { h = append(h, b+toLower) } else { h = append(h, b) } - } else if b == '-' || b == '_' || (b >= '0' && b <= '9') { + case b == '-' || b == '_' || (b >= '0' && b <= '9'): h = append(h, b) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 2ef0b9c31..32f1514cb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -836,9 +836,10 @@ "revisionTime": "2018-08-08T20:39:25Z" }, { + "checksumSHA1": "hev23KrSfQ4KfosxH+gJKziFVtA=", "path": "github.com/rs/cors", - "revision": "a62a804a8a009876ca59105f7899938a1349f4b3", - "revisionTime": "2016-06-18T04:49:35+05:30" + "revision": "76f58f330d76a55c5badc74f6212e8a15e742c77", + "revisionTime": "2019-01-16T17:59:10Z" }, { "path": "github.com/rs/xhandler",