diff --git a/go.mod b/go.mod index dfa6a59e..032350fd 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/google/uuid v1.1.1 - github.com/gorilla/csrf v1.6.0 + github.com/gorilla/csrf v1.7.3 github.com/gorilla/mux v1.7.3 github.com/gorilla/sessions v1.2.0 github.com/gorilla/websocket v1.4.1 @@ -27,8 +27,7 @@ require ( require ( github.com/go-jose/go-jose/v4 v4.0.1 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/pkg/errors v0.8.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect ) diff --git a/go.sum b/go.sum index b43d7b47..84be3d2d 100644 --- a/go.sum +++ b/go.sum @@ -15,14 +15,17 @@ github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWq github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.6.0 h1:60oN1cFdncCE8tjwQ3QEkFND5k37lQPcRjnlvm7CIJ0= -github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= +github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0= +github.com/gorilla/csrf v1.7.3/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -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/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= @@ -35,8 +38,6 @@ github.com/libvirt/libvirt-go v7.4.0+incompatible h1:crnSLkwPqCdXtg6jib/FxBG/hwe github.com/libvirt/libvirt-go v7.4.0+incompatible/go.mod h1:34zsnB4iGeOv7Byj6qotuW8Ya4v4Tr43ttjz/F0wjLE= github.com/libvirt/libvirt-go-xml v7.4.0+incompatible h1:+BBo2XjlT8pAK4pm+aSX8mC/6nc/rdRac10ZukpW31U= github.com/libvirt/libvirt-go-xml v7.4.0+incompatible/go.mod h1:oBlgD3xOA01ihiK5stbhFzvieyW+jVS6kbbsMVF623A= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/vendor/github.com/gorilla/csrf/.editorconfig b/vendor/github.com/gorilla/csrf/.editorconfig new file mode 100644 index 00000000..c6b74c3e --- /dev/null +++ b/vendor/github.com/gorilla/csrf/.editorconfig @@ -0,0 +1,20 @@ +; https://editorconfig.org/ + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go,.gitmodules}] +indent_style = tab +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +eclint_indent_style = unset \ No newline at end of file diff --git a/vendor/github.com/gorilla/csrf/.gitignore b/vendor/github.com/gorilla/csrf/.gitignore new file mode 100644 index 00000000..84039fec --- /dev/null +++ b/vendor/github.com/gorilla/csrf/.gitignore @@ -0,0 +1 @@ +coverage.coverprofile diff --git a/vendor/github.com/gorilla/csrf/AUTHORS b/vendor/github.com/gorilla/csrf/AUTHORS deleted file mode 100644 index 4e84c378..00000000 --- a/vendor/github.com/gorilla/csrf/AUTHORS +++ /dev/null @@ -1,20 +0,0 @@ -# This is the official list of gorilla/csrf authors for copyright purposes. -# Please keep the list sorted. - -adiabatic -Google LLC (https://opensource.google.com) -jamesgroat -Joshua Carp -Kamil Kisiel -Kevin Burke -Kévin Dunglas -Kristoffer Berdal -Martin Angers -Matt Silverlock -Philip I. Thomas -Richard Musiol -Seth Hoenig -Stefano Vettorazzi -Wayne Ashley Berry -田浩浩 -陈东海 diff --git a/vendor/github.com/gorilla/csrf/LICENSE b/vendor/github.com/gorilla/csrf/LICENSE index c1eb344c..bb9d80bc 100644 --- a/vendor/github.com/gorilla/csrf/LICENSE +++ b/vendor/github.com/gorilla/csrf/LICENSE @@ -1,26 +1,27 @@ -Copyright (c) 2015-2018, The Gorilla Authors. All rights reserved. +Copyright (c) 2023 The Gorilla Authors. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/csrf/Makefile b/vendor/github.com/gorilla/csrf/Makefile new file mode 100644 index 00000000..ac37ffd3 --- /dev/null +++ b/vendor/github.com/gorilla/csrf/Makefile @@ -0,0 +1,34 @@ +GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') +GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +GO_SEC=$(shell which gosec 2> /dev/null || echo '') +GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest + +GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') +GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest + +.PHONY: golangci-lint +golangci-lint: + $(if $(GO_LINT), ,go install $(GO_LINT_URI)) + @echo "##### Running golangci-lint" + golangci-lint run -v + +.PHONY: gosec +gosec: + $(if $(GO_SEC), ,go install $(GO_SEC_URI)) + @echo "##### Running gosec" + gosec ./... + +.PHONY: govulncheck +govulncheck: + $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) + @echo "##### Running govulncheck" + govulncheck ./... + +.PHONY: verify +verify: golangci-lint gosec govulncheck + +.PHONY: test +test: + @echo "##### Running tests" + go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... diff --git a/vendor/github.com/gorilla/csrf/README.md b/vendor/github.com/gorilla/csrf/README.md index 7265ad2e..d768c810 100644 --- a/vendor/github.com/gorilla/csrf/README.md +++ b/vendor/github.com/gorilla/csrf/README.md @@ -1,6 +1,12 @@ # 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) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) +![testing](https://github.com/gorilla/csrf/actions/workflows/test.yml/badge.svg) +[![codecov](https://codecov.io/github/gorilla/csrf/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/csrf) +[![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) + + +![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) gorilla/csrf is a HTTP middleware library that provides [cross-site request forgery](http://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/) (CSRF) @@ -26,6 +32,18 @@ gorilla/csrf is designed to work with any Go web framework, including: gorilla/csrf is also compatible with middleware 'helper' libraries like [Alice](https://github.com/justinas/alice) and [Negroni](https://github.com/codegangsta/negroni). +## Contents + + * [Install](#install) + * [Examples](#examples) + + [HTML Forms](#html-forms) + + [JavaScript Applications](#javascript-applications) + + [Google App Engine](#google-app-engine) + + [Setting SameSite](#setting-samesite) + + [Setting Options](#setting-options) + * [Design Notes](#design-notes) + * [License](#license) + ## Install With a properly configured Go toolchain: @@ -39,6 +57,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 @@ -52,9 +71,12 @@ http.ListenAndServe(":8000", CSRF(r)) ...and then collect the token with `csrf.Token(r)` in your handlers before passing it to the template, JSON body or HTTP header (see below). -Note that the authentication key passed to `csrf.Protect([]byte(key))` should be -32-bytes long and persist across application restarts. Generating a random key -won't allow you to authenticate existing cookies and will break your CSRF +Note that the authentication key passed to `csrf.Protect([]byte(key))` should: +- be 32-bytes long +- persist across application restarts. +- kept secret from potential malicious users - do not hardcode it into the source code, especially not in open-source applications. + +Generating a random key won't allow you to authenticate existing cookies and will break your CSRF validation. gorilla/csrf inspects the HTTP headers (first) and form body (second) on @@ -196,6 +218,49 @@ try { } ``` +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.TrustedOrigins([]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 @@ -221,6 +286,45 @@ 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)) +} +``` + +### Cookie path + +By default, CSRF cookies are set on the path of the request. + +This can create issues, if the request is done from one path to a different path. + +You might want to set up a root path for all the cookies; that way, the CSRF will always work across all your paths. + +``` + CSRF := csrf.Protect( + []byte("a-32-byte-long-key-goes-here"), + csrf.Path("/"), + ) +``` + ### Setting Options What about providing your own error handler and changing the HTTP header the @@ -268,6 +372,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.go b/vendor/github.com/gorilla/csrf/context.go index d8bb42f0..d24b146e 100644 --- a/vendor/github.com/gorilla/csrf/context.go +++ b/vendor/github.com/gorilla/csrf/context.go @@ -1,26 +1,25 @@ +//go:build go1.7 // +build go1.7 package csrf import ( "context" + "fmt" "net/http" - - "github.com/pkg/errors" ) func contextGet(r *http.Request, key string) (interface{}, error) { val := r.Context().Value(key) if val == nil { - return nil, errors.Errorf("no value exists in the context for key %q", key) + return nil, fmt.Errorf("no value exists in the context for key %q", key) } - return val, nil } func contextSave(r *http.Request, key string, val interface{}) *http.Request { ctx := r.Context() - ctx = context.WithValue(ctx, key, val) + ctx = context.WithValue(ctx, key, val) // nolint:staticcheck return r.WithContext(ctx) } diff --git a/vendor/github.com/gorilla/csrf/csrf.go b/vendor/github.com/gorilla/csrf/csrf.go index cc7878f4..5dda2547 100644 --- a/vendor/github.com/gorilla/csrf/csrf.go +++ b/vendor/github.com/gorilla/csrf/csrf.go @@ -1,11 +1,12 @@ package csrf import ( + "context" + "errors" "fmt" "net/http" "net/url" - - "github.com/pkg/errors" + "slices" "github.com/gorilla/securecookie" ) @@ -15,14 +16,22 @@ const tokenLength = 32 // Context/session keys & prefixes const ( - tokenKey string = "gorilla.csrf.Token" - formKey string = "gorilla.csrf.Form" + tokenKey string = "gorilla.csrf.Token" // #nosec G101 + formKey string = "gorilla.csrf.Form" // #nosec G101 errorKey string = "gorilla.csrf.Error" skipCheckKey string = "gorilla.csrf.Skip" cookieName string = "_gorilla_csrf" errorPrefix string = "gorilla/csrf: " ) +type contextKey string + +// PlaintextHTTPContextKey is the context key used to store whether the request +// is being served via plaintext HTTP. This is used to signal to the middleware +// that strict Referer checking should not be enforced as is done for HTTPS by +// default. +const PlaintextHTTPContextKey contextKey = "plaintext" + var ( // The name value used in form fields. fieldName = tokenKey @@ -42,6 +51,9 @@ var ( // ErrNoReferer is returned when a HTTPS request provides an empty Referer // header. ErrNoReferer = errors.New("referer not supplied") + // ErrBadOrigin is returned when the Origin header is present and is not a + // trusted origin. + ErrBadOrigin = errors.New("origin invalid") // ErrBadReferer is returned when the scheme & host in the URL do not match // the supplied Referer header. ErrBadReferer = errors.New("referer invalid") @@ -52,6 +64,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 +98,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 @@ -86,6 +120,7 @@ type options struct { // 'Forbidden' error response. // // Example: +// // package main // // import ( @@ -122,7 +157,6 @@ type options struct { // // This is useful if you're sending JSON to clients or a front-end JavaScript // // framework. // } -// func Protect(authKey []byte, opts ...Option) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { cs := parseOptions(h, opts...) @@ -165,6 +199,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, @@ -220,10 +255,50 @@ func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) { // HTTP methods not defined as idempotent ("safe") under RFC7231 require // inspection. if !contains(safeMethods, r.Method) { - // Enforce an origin check for HTTPS connections. As per the Django CSRF - // implementation (https://goo.gl/vKA7GE) the Referer header is almost - // always present for same-domain HTTP requests. - if r.URL.Scheme == "https" { + var isPlaintext bool + val := r.Context().Value(PlaintextHTTPContextKey) + if val != nil { + isPlaintext, _ = val.(bool) + } + + // take a copy of the request URL to avoid mutating the original + // attached to the request. + // set the scheme & host based on the request context as these are not + // populated by default for server requests + // ref: https://pkg.go.dev/net/http#Request + requestURL := *r.URL // shallow clone + + requestURL.Scheme = "https" + if isPlaintext { + requestURL.Scheme = "http" + } + if requestURL.Host == "" { + requestURL.Host = r.Host + } + + // if we have an Origin header, check it against our allowlist + origin := r.Header.Get("Origin") + if origin != "" { + parsedOrigin, err := url.Parse(origin) + if err != nil { + r = envError(r, ErrBadOrigin) + cs.opts.ErrorHandler.ServeHTTP(w, r) + return + } + if !sameOrigin(&requestURL, parsedOrigin) && !slices.Contains(cs.opts.TrustedOrigins, parsedOrigin.Host) { + r = envError(r, ErrBadOrigin) + cs.opts.ErrorHandler.ServeHTTP(w, r) + return + } + } + + // If we are serving via TLS and have no Origin header, prevent against + // CSRF via HTTP machine in the middle attacks by enforcing strict + // Referer origin checks. Consider an attacker who performs a + // successful HTTP Machine-in-the-Middle attack and uses this to inject + // a form and cause submission to our origin. We strictly disallow + // cleartext HTTP origins and evaluate the domain against an allowlist. + if origin == "" && !isPlaintext { // Fetch the Referer value. Call the error handler if it's empty or // otherwise fails to parse. referer, err := url.Parse(r.Referer()) @@ -233,23 +308,39 @@ func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if sameOrigin(r.URL, referer) == false { + // disallow cleartext HTTP referers when serving via TLS + if referer.Scheme == "http" { + r = envError(r, ErrBadReferer) + cs.opts.ErrorHandler.ServeHTTP(w, r) + return + } + + // If the request is being served via TLS and the Referer is not the + // same origin, check the domain against our allowlist. We only + // check when we have host information from the referer. + if referer.Host != "" && referer.Host != r.Host && !slices.Contains(cs.opts.TrustedOrigins, referer.Host) { r = envError(r, ErrBadReferer) cs.opts.ErrorHandler.ServeHTTP(w, r) return } } - // If the token returned from the session store is nil for non-idempotent - // ("unsafe") methods, call the error handler. - if realToken == nil { + // Retrieve the combined token (pad + masked) token... + maskedToken, err := cs.requestToken(r) + if err != nil { + r = envError(r, ErrBadToken) + cs.opts.ErrorHandler.ServeHTTP(w, r) + return + } + + if maskedToken == nil { r = envError(r, ErrNoToken) cs.opts.ErrorHandler.ServeHTTP(w, r) return } - // Retrieve the combined token (pad + masked) token and unmask it. - requestToken := unmask(cs.requestToken(r)) + // ... and unmask it. + requestToken := unmask(maskedToken) // Compare the request token against the real token if !compareTokens(requestToken, realToken) { @@ -269,11 +360,19 @@ func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) { contextClear(r) } +// PlaintextHTTPRequest accepts as input a http.Request and returns a new +// http.Request with the PlaintextHTTPContextKey set to true. This is used to +// signal to the CSRF middleware that the request is being served over plaintext +// HTTP and that Referer-based origin allow-listing checks should be skipped. +func PlaintextHTTPRequest(r *http.Request) *http.Request { + ctx := context.WithValue(r.Context(), PlaintextHTTPContextKey, true) + return r.WithContext(ctx) +} + // unauthorizedhandler sets a HTTP 403 Forbidden status and writes the // CSRF failure reason to the response. func unauthorizedHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("%s - %s", http.StatusText(http.StatusForbidden), FailureReason(r)), http.StatusForbidden) - return } diff --git a/vendor/github.com/gorilla/csrf/helpers.go b/vendor/github.com/gorilla/csrf/helpers.go index 3dacfd21..99005ee5 100644 --- a/vendor/github.com/gorilla/csrf/helpers.go +++ b/vendor/github.com/gorilla/csrf/helpers.go @@ -51,18 +51,17 @@ func UnsafeSkipCheck(r *http.Request) *http.Request { // // Example: // -// // The following tag in our form.tmpl template: -// {{ .csrfField }} -// -// // ... becomes: -// +// // The following tag in our form.tmpl template: +// {{ .csrfField }} // +// // ... becomes: +// func TemplateField(r *http.Request) template.HTML { if name, err := contextGet(r, formKey); err == nil { fragment := fmt.Sprintf(``, name, Token(r)) - return template.HTML(fragment) + return template.HTML(fragment) // #nosec G203 } return template.HTML("") @@ -75,7 +74,7 @@ func TemplateField(r *http.Request) template.HTML { // token and returning them together as a 64-byte slice. This effectively // randomises the token on a per-request basis without breaking multiple browser // tabs/windows. -func mask(realToken []byte, r *http.Request) string { +func mask(realToken []byte, _ *http.Request) string { otp, err := generateRandomBytes(tokenLength) if err != nil { return "" @@ -105,7 +104,7 @@ func unmask(issued []byte) []byte { // requestToken returns the issued token (pad + masked token) from the HTTP POST // body or HTTP header. It will return nil if the token fails to decode. -func (cs *csrf) requestToken(r *http.Request) []byte { +func (cs *csrf) requestToken(r *http.Request) ([]byte, error) { // 1. Check the HTTP header first. issued := r.Header.Get(cs.opts.RequestHeader) @@ -123,14 +122,19 @@ func (cs *csrf) requestToken(r *http.Request) []byte { } } + // Return nil (equivalent to empty byte slice) if no token was found + if issued == "" { + return nil, nil + } + // Decode the "issued" (pad + masked) token sent in the request. Return a // nil byte slice on a decoding error (this will fail upstream). decoded, err := base64.StdEncoding.DecodeString(issued) if err != nil { - return nil + return nil, err } - return decoded + return decoded, nil } // generateRandomBytes returns securely generated random bytes. diff --git a/vendor/github.com/gorilla/csrf/options.go b/vendor/github.com/gorilla/csrf/options.go index b50ebd4e..c61d3012 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 39f47ad7..4e6382bd 100644 --- a/vendor/github.com/gorilla/csrf/store.go +++ b/vendor/github.com/gorilla/csrf/store.go @@ -1,3 +1,6 @@ +//go:build go1.11 +// +build go1.11 + package csrf import ( @@ -28,6 +31,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 +67,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 00000000..4e1fb9e7 --- /dev/null +++ b/vendor/github.com/gorilla/csrf/store_legacy.go @@ -0,0 +1,88 @@ +//go:build !go1.11 +// +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/github.com/gorilla/securecookie/.editorconfig b/vendor/github.com/gorilla/securecookie/.editorconfig new file mode 100644 index 00000000..2940ec92 --- /dev/null +++ b/vendor/github.com/gorilla/securecookie/.editorconfig @@ -0,0 +1,20 @@ +; https://editorconfig.org/ + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go,.gitmodules}] +indent_style = tab +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +eclint_indent_style = unset diff --git a/vendor/github.com/gorilla/securecookie/.gitignore b/vendor/github.com/gorilla/securecookie/.gitignore new file mode 100644 index 00000000..84039fec --- /dev/null +++ b/vendor/github.com/gorilla/securecookie/.gitignore @@ -0,0 +1 @@ +coverage.coverprofile diff --git a/vendor/github.com/gorilla/securecookie/.travis.yml b/vendor/github.com/gorilla/securecookie/.travis.yml deleted file mode 100644 index 6f440f1e..00000000 --- a/vendor/github.com/gorilla/securecookie/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: go -sudo: false - -matrix: - include: - - go: 1.3 - - go: 1.4 - - go: 1.5 - - go: 1.6 - - go: 1.7 - - go: tip - allow_failures: - - go: tip - -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - go vet $(go list ./... | grep -v /vendor/) - - go test -v -race ./... diff --git a/vendor/github.com/gorilla/securecookie/LICENSE b/vendor/github.com/gorilla/securecookie/LICENSE index 0e5fb872..bb9d80bc 100644 --- a/vendor/github.com/gorilla/securecookie/LICENSE +++ b/vendor/github.com/gorilla/securecookie/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. +Copyright (c) 2023 The Gorilla Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/vendor/github.com/gorilla/securecookie/Makefile b/vendor/github.com/gorilla/securecookie/Makefile new file mode 100644 index 00000000..2b9008a2 --- /dev/null +++ b/vendor/github.com/gorilla/securecookie/Makefile @@ -0,0 +1,39 @@ +GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') +GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +GO_SEC=$(shell which gosec 2> /dev/null || echo '') +GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest + +GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') +GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest + +.PHONY: golangci-lint +golangci-lint: + $(if $(GO_LINT), ,go install $(GO_LINT_URI)) + @echo "##### Running golangci-lint" + golangci-lint run -v + +.PHONY: gosec +gosec: + $(if $(GO_SEC), ,go install $(GO_SEC_URI)) + @echo "##### Running gosec" + gosec ./... + +.PHONY: govulncheck +govulncheck: + $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) + @echo "##### Running govulncheck" + govulncheck ./... + +.PHONY: verify +verify: golangci-lint gosec govulncheck + +.PHONY: test +test: + @echo "##### Running tests" + go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... + +.PHONY: fuzz +fuzz: + @echo "##### Running fuzz tests" + go test -v -fuzz FuzzEncodeDecode -fuzztime 60s diff --git a/vendor/github.com/gorilla/securecookie/README.md b/vendor/github.com/gorilla/securecookie/README.md index aa7bd1a5..c3b9815d 100644 --- a/vendor/github.com/gorilla/securecookie/README.md +++ b/vendor/github.com/gorilla/securecookie/README.md @@ -1,10 +1,13 @@ -securecookie -============ -[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie) -[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge) +# gorilla/securecookie +![testing](https://github.com/gorilla/securecookie/actions/workflows/test.yml/badge.svg) +[![codecov](https://codecov.io/github/gorilla/securecookie/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/securecookie) +[![godoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) +[![sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge) -securecookie encodes and decodes authenticated and optionally encrypted +![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) + +securecookie encodes and decodes authenticated and optionally encrypted cookie values. Secure cookies can't be forged, because their values are validated using HMAC. @@ -33,7 +36,10 @@ to not use encryption. If set, the length must correspond to the block size of the encryption algorithm. For AES, used by default, valid lengths are 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. -Strong keys can be created using the convenience function GenerateRandomKey(). +Strong keys can be created using the convenience function +`GenerateRandomKey()`. Note that keys created using `GenerateRandomKey()` are not +automatically persisted. New keys will be created when the application is +restarted, and previously issued cookies will not be able to be decoded. Once a SecureCookie instance is set, use it to encode a cookie value: @@ -75,6 +81,64 @@ registered first using gob.Register(). For basic types this is not needed; it works out of the box. An optional JSON encoder that uses `encoding/json` is available for types compatible with JSON. +### Key Rotation +Rotating keys is an important part of any security strategy. The `EncodeMulti` and +`DecodeMulti` functions allow for multiple keys to be rotated in and out. +For example, let's take a system that stores keys in a map: + +```go +// keys stored in a map will not be persisted between restarts +// a more persistent storage should be considered for production applications. +var cookies = map[string]*securecookie.SecureCookie{ + "previous": securecookie.New( + securecookie.GenerateRandomKey(64), + securecookie.GenerateRandomKey(32), + ), + "current": securecookie.New( + securecookie.GenerateRandomKey(64), + securecookie.GenerateRandomKey(32), + ), +} +``` + +Using the current key to encode new cookies: +```go +func SetCookieHandler(w http.ResponseWriter, r *http.Request) { + value := map[string]string{ + "foo": "bar", + } + if encoded, err := securecookie.EncodeMulti("cookie-name", value, cookies["current"]); err == nil { + cookie := &http.Cookie{ + Name: "cookie-name", + Value: encoded, + Path: "/", + } + http.SetCookie(w, cookie) + } +} +``` + +Later, decode cookies. Check against all valid keys: +```go +func ReadCookieHandler(w http.ResponseWriter, r *http.Request) { + if cookie, err := r.Cookie("cookie-name"); err == nil { + value := make(map[string]string) + err = securecookie.DecodeMulti("cookie-name", cookie.Value, &value, cookies["current"], cookies["previous"]) + if err == nil { + fmt.Fprintf(w, "The value of foo is %q", value["foo"]) + } + } +} +``` + +Rotate the keys. This strategy allows previously issued cookies to be valid until the next rotation: +```go +func Rotate(newCookie *securecookie.SecureCookie) { + cookies["previous"] = cookies["current"] + cookies["current"] = newCookie +} +``` + ## License BSD licensed. See the LICENSE file for details. diff --git a/vendor/github.com/gorilla/securecookie/fuzz.go b/vendor/github.com/gorilla/securecookie/fuzz.go deleted file mode 100644 index e4d0534e..00000000 --- a/vendor/github.com/gorilla/securecookie/fuzz.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build gofuzz - -package securecookie - -var hashKey = []byte("very-secret12345") -var blockKey = []byte("a-lot-secret1234") -var s = New(hashKey, blockKey) - -type Cookie struct { - B bool - I int - S string -} - -func Fuzz(data []byte) int { - datas := string(data) - var c Cookie - if err := s.Decode("fuzz", datas, &c); err != nil { - return 0 - } - if _, err := s.Encode("fuzz", c); err != nil { - panic(err) - } - return 1 -} diff --git a/vendor/github.com/gorilla/securecookie/securecookie.go b/vendor/github.com/gorilla/securecookie/securecookie.go index cd4e0976..4d5ea860 100644 --- a/vendor/github.com/gorilla/securecookie/securecookie.go +++ b/vendor/github.com/gorilla/securecookie/securecookie.go @@ -124,7 +124,7 @@ type Codec interface { // GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes. // // blockKey is optional, used to encrypt values. Create it using -// GenerateRandomKey(). The key length must correspond to the block size +// GenerateRandomKey(). The key length must correspond to the key size // of the encryption algorithm. For AES, used by default, valid lengths are // 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. // The default encoder used for cookie serialization is encoding/gob. @@ -141,7 +141,7 @@ func New(hashKey, blockKey []byte) *SecureCookie { maxLength: 4096, sz: GobEncoder{}, } - if hashKey == nil { + if len(hashKey) == 0 { s.err = errHashKeyNotSet } if blockKey != nil { @@ -286,7 +286,7 @@ func (s *SecureCookie) Encode(name string, value interface{}) (string, error) { b = encode(b) // 5. Check length. if s.maxLength != 0 && len(b) > s.maxLength { - return "", errEncodedValueTooLong + return "", fmt.Errorf("%s: %d", errEncodedValueTooLong, len(b)) } // Done. return string(b), nil @@ -310,7 +310,7 @@ func (s *SecureCookie) Decode(name, value string, dst interface{}) error { } // 1. Check length. if s.maxLength != 0 && len(value) > s.maxLength { - return errValueToDecodeTooLong + return fmt.Errorf("%s: %d", errValueToDecodeTooLong, len(value)) } // 2. Decode from base64. b, err := decode([]byte(value)) @@ -391,7 +391,7 @@ func verifyMac(h hash.Hash, value []byte, mac []byte) error { // encrypt encrypts a value using the given block in counter mode. // -// A random initialization vector (http://goo.gl/zF67k) with the length of the +// A random initialization vector ( https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Initialization_vector_(IV) ) with the length of the // block size is prepended to the resulting ciphertext. func encrypt(block cipher.Block, value []byte) ([]byte, error) { iv := GenerateRandomKey(block.BlockSize()) @@ -408,7 +408,7 @@ func encrypt(block cipher.Block, value []byte) ([]byte, error) { // decrypt decrypts a value using the given block in counter mode. // // The value to be decrypted must be prepended by a initialization vector -// (http://goo.gl/zF67k) with the length of the block size. +// ( https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Initialization_vector_(IV) ) with the length of the block size. func decrypt(block cipher.Block, value []byte) ([]byte, error) { size := block.BlockSize() if len(value) > size { @@ -506,6 +506,10 @@ func decode(value []byte) ([]byte, error) { // GenerateRandomKey creates a random key with the given length in bytes. // On failure, returns nil. // +// Note that keys created using `GenerateRandomKey()` are not automatically +// persisted. New keys will be created when the application is restarted, and +// previously issued cookies will not be able to be decoded. +// // Callers should explicitly check for the possibility of a nil return, treat // it as a failure of the system random number generator, and not continue. func GenerateRandomKey(length int) []byte { @@ -525,22 +529,21 @@ func GenerateRandomKey(length int) []byte { // // Example: // -// codecs := securecookie.CodecsFromPairs( -// []byte("new-hash-key"), -// []byte("new-block-key"), -// []byte("old-hash-key"), -// []byte("old-block-key"), -// ) -// -// // Modify each instance. -// for _, s := range codecs { -// if cookie, ok := s.(*securecookie.SecureCookie); ok { -// cookie.MaxAge(86400 * 7) -// cookie.SetSerializer(securecookie.JSONEncoder{}) -// cookie.HashFunc(sha512.New512_256) -// } -// } +// codecs := securecookie.CodecsFromPairs( +// []byte("new-hash-key"), +// []byte("new-block-key"), +// []byte("old-hash-key"), +// []byte("old-block-key"), +// ) // +// // Modify each instance. +// for _, s := range codecs { +// if cookie, ok := s.(*securecookie.SecureCookie); ok { +// cookie.MaxAge(86400 * 7) +// cookie.SetSerializer(securecookie.JSONEncoder{}) +// cookie.HashFunc(sha512.New512_256) +// } +// } func CodecsFromPairs(keyPairs ...[]byte) []Codec { codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2) for i := 0; i < len(keyPairs); i += 2 { diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore deleted file mode 100644 index daf913b1..00000000 --- a/vendor/github.com/pkg/errors/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml deleted file mode 100644 index d4b92663..00000000 --- a/vendor/github.com/pkg/errors/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: go -go_import_path: github.com/pkg/errors -go: - - 1.4.x - - 1.5.x - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x - - tip - -script: - - go test -v ./... diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE deleted file mode 100644 index 835ba3e7..00000000 --- a/vendor/github.com/pkg/errors/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2015, Dave Cheney -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md deleted file mode 100644 index 6483ba2a..00000000 --- a/vendor/github.com/pkg/errors/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) - -Package errors provides simple error handling primitives. - -`go get github.com/pkg/errors` - -The traditional error handling idiom in Go is roughly akin to -```go -if err != nil { - return err -} -``` -which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. - -## Adding context to an error - -The errors.Wrap function returns a new error that adds context to the original error. For example -```go -_, err := ioutil.ReadAll(r) -if err != nil { - return errors.Wrap(err, "read failed") -} -``` -## Retrieving the cause of an error - -Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. -```go -type causer interface { - Cause() error -} -``` -`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: -```go -switch err := errors.Cause(err).(type) { -case *MyError: - // handle specifically -default: - // unknown error -} -``` - -[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). - -## Contributing - -We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. - -Before proposing a change, please discuss your change by raising an issue. - -## License - -BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml deleted file mode 100644 index a932eade..00000000 --- a/vendor/github.com/pkg/errors/appveyor.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: build-{build}.{branch} - -clone_folder: C:\gopath\src\github.com\pkg\errors -shallow_clone: true # for startup speed - -environment: - GOPATH: C:\gopath - -platform: - - x64 - -# http://www.appveyor.com/docs/installed-software -install: - # some helpful output for debugging builds - - go version - - go env - # pre-installed MinGW at C:\MinGW is 32bit only - # but MSYS2 at C:\msys64 has mingw64 - - set PATH=C:\msys64\mingw64\bin;%PATH% - - gcc --version - - g++ --version - -build_script: - - go install -v ./... - -test_script: - - set PATH=C:\gopath\bin;%PATH% - - go test -v ./... - -#artifacts: -# - path: '%GOPATH%\bin\*.exe' -deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go deleted file mode 100644 index 7421f326..00000000 --- a/vendor/github.com/pkg/errors/errors.go +++ /dev/null @@ -1,282 +0,0 @@ -// Package errors provides simple error handling primitives. -// -// The traditional error handling idiom in Go is roughly akin to -// -// if err != nil { -// return err -// } -// -// which when applied recursively up the call stack results in error reports -// without context or debugging information. The errors package allows -// programmers to add context to the failure path in their code in a way -// that does not destroy the original value of the error. -// -// Adding context to an error -// -// The errors.Wrap function returns a new error that adds context to the -// original error by recording a stack trace at the point Wrap is called, -// together with the supplied message. For example -// -// _, err := ioutil.ReadAll(r) -// if err != nil { -// return errors.Wrap(err, "read failed") -// } -// -// If additional control is required, the errors.WithStack and -// errors.WithMessage functions destructure errors.Wrap into its component -// operations: annotating an error with a stack trace and with a message, -// respectively. -// -// Retrieving the cause of an error -// -// Using errors.Wrap constructs a stack of errors, adding context to the -// preceding error. Depending on the nature of the error it may be necessary -// to reverse the operation of errors.Wrap to retrieve the original error -// for inspection. Any error value which implements this interface -// -// type causer interface { -// Cause() error -// } -// -// can be inspected by errors.Cause. errors.Cause will recursively retrieve -// the topmost error that does not implement causer, which is assumed to be -// the original cause. For example: -// -// switch err := errors.Cause(err).(type) { -// case *MyError: -// // handle specifically -// default: -// // unknown error -// } -// -// Although the causer interface is not exported by this package, it is -// considered a part of its stable public interface. -// -// Formatted printing of errors -// -// All error values returned from this package implement fmt.Formatter and can -// be formatted by the fmt package. The following verbs are supported: -// -// %s print the error. If the error has a Cause it will be -// printed recursively. -// %v see %s -// %+v extended format. Each Frame of the error's StackTrace will -// be printed in detail. -// -// Retrieving the stack trace of an error or wrapper -// -// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are -// invoked. This information can be retrieved with the following interface: -// -// type stackTracer interface { -// StackTrace() errors.StackTrace -// } -// -// The returned errors.StackTrace type is defined as -// -// type StackTrace []Frame -// -// The Frame type represents a call site in the stack trace. Frame supports -// the fmt.Formatter interface that can be used for printing information about -// the stack trace of this error. For example: -// -// if err, ok := err.(stackTracer); ok { -// for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) -// } -// } -// -// Although the stackTracer interface is not exported by this package, it is -// considered a part of its stable public interface. -// -// See the documentation for Frame.Format for more details. -package errors - -import ( - "fmt" - "io" -) - -// New returns an error with the supplied message. -// New also records the stack trace at the point it was called. -func New(message string) error { - return &fundamental{ - msg: message, - stack: callers(), - } -} - -// Errorf formats according to a format specifier and returns the string -// as a value that satisfies error. -// Errorf also records the stack trace at the point it was called. -func Errorf(format string, args ...interface{}) error { - return &fundamental{ - msg: fmt.Sprintf(format, args...), - stack: callers(), - } -} - -// fundamental is an error that has a message and a stack, but no caller. -type fundamental struct { - msg string - *stack -} - -func (f *fundamental) Error() string { return f.msg } - -func (f *fundamental) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - io.WriteString(s, f.msg) - f.stack.Format(s, verb) - return - } - fallthrough - case 's': - io.WriteString(s, f.msg) - case 'q': - fmt.Fprintf(s, "%q", f.msg) - } -} - -// WithStack annotates err with a stack trace at the point WithStack was called. -// If err is nil, WithStack returns nil. -func WithStack(err error) error { - if err == nil { - return nil - } - return &withStack{ - err, - callers(), - } -} - -type withStack struct { - error - *stack -} - -func (w *withStack) Cause() error { return w.error } - -func (w *withStack) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - fmt.Fprintf(s, "%+v", w.Cause()) - w.stack.Format(s, verb) - return - } - fallthrough - case 's': - io.WriteString(s, w.Error()) - case 'q': - fmt.Fprintf(s, "%q", w.Error()) - } -} - -// Wrap returns an error annotating err with a stack trace -// at the point Wrap is called, and the supplied message. -// If err is nil, Wrap returns nil. -func Wrap(err error, message string) error { - if err == nil { - return nil - } - err = &withMessage{ - cause: err, - msg: message, - } - return &withStack{ - err, - callers(), - } -} - -// Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is called, and the format specifier. -// If err is nil, Wrapf returns nil. -func Wrapf(err error, format string, args ...interface{}) error { - if err == nil { - return nil - } - err = &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - } - return &withStack{ - err, - callers(), - } -} - -// WithMessage annotates err with a new message. -// If err is nil, WithMessage returns nil. -func WithMessage(err error, message string) error { - if err == nil { - return nil - } - return &withMessage{ - cause: err, - msg: message, - } -} - -// WithMessagef annotates err with the format specifier. -// If err is nil, WithMessagef returns nil. -func WithMessagef(err error, format string, args ...interface{}) error { - if err == nil { - return nil - } - return &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - } -} - -type withMessage struct { - cause error - msg string -} - -func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } -func (w *withMessage) Cause() error { return w.cause } - -func (w *withMessage) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - fmt.Fprintf(s, "%+v\n", w.Cause()) - io.WriteString(s, w.msg) - return - } - fallthrough - case 's', 'q': - io.WriteString(s, w.Error()) - } -} - -// Cause returns the underlying cause of the error, if possible. -// An error value has a cause if it implements the following -// interface: -// -// type causer interface { -// Cause() error -// } -// -// If the error does not implement Cause, the original error will -// be returned. If the error is nil, nil will be returned without further -// investigation. -func Cause(err error) error { - type causer interface { - Cause() error - } - - for err != nil { - cause, ok := err.(causer) - if !ok { - break - } - err = cause.Cause() - } - return err -} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go deleted file mode 100644 index 2874a048..00000000 --- a/vendor/github.com/pkg/errors/stack.go +++ /dev/null @@ -1,147 +0,0 @@ -package errors - -import ( - "fmt" - "io" - "path" - "runtime" - "strings" -) - -// Frame represents a program counter inside a stack frame. -type Frame uintptr - -// pc returns the program counter for this frame; -// multiple frames may have the same PC value. -func (f Frame) pc() uintptr { return uintptr(f) - 1 } - -// file returns the full path to the file that contains the -// function for this Frame's pc. -func (f Frame) file() string { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return "unknown" - } - file, _ := fn.FileLine(f.pc()) - return file -} - -// line returns the line number of source code of the -// function for this Frame's pc. -func (f Frame) line() int { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return 0 - } - _, line := fn.FileLine(f.pc()) - return line -} - -// Format formats the frame according to the fmt.Formatter interface. -// -// %s source file -// %d source line -// %n function name -// %v equivalent to %s:%d -// -// Format accepts flags that alter the printing of some verbs, as follows: -// -// %+s function name and path of source file relative to the compile time -// GOPATH separated by \n\t (\n\t) -// %+v equivalent to %+s:%d -func (f Frame) Format(s fmt.State, verb rune) { - switch verb { - case 's': - switch { - case s.Flag('+'): - pc := f.pc() - fn := runtime.FuncForPC(pc) - if fn == nil { - io.WriteString(s, "unknown") - } else { - file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) - } - default: - io.WriteString(s, path.Base(f.file())) - } - case 'd': - fmt.Fprintf(s, "%d", f.line()) - case 'n': - name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) - case 'v': - f.Format(s, 's') - io.WriteString(s, ":") - f.Format(s, 'd') - } -} - -// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). -type StackTrace []Frame - -// Format formats the stack of Frames according to the fmt.Formatter interface. -// -// %s lists source files for each Frame in the stack -// %v lists the source file and line number for each Frame in the stack -// -// Format accepts flags that alter the printing of some verbs, as follows: -// -// %+v Prints filename, function, and line number for each Frame in the stack. -func (st StackTrace) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - switch { - case s.Flag('+'): - for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) - } - case s.Flag('#'): - fmt.Fprintf(s, "%#v", []Frame(st)) - default: - fmt.Fprintf(s, "%v", []Frame(st)) - } - case 's': - fmt.Fprintf(s, "%s", []Frame(st)) - } -} - -// stack represents a stack of program counters. -type stack []uintptr - -func (s *stack) Format(st fmt.State, verb rune) { - switch verb { - case 'v': - switch { - case st.Flag('+'): - for _, pc := range *s { - f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) - } - } - } -} - -func (s *stack) StackTrace() StackTrace { - f := make([]Frame, len(*s)) - for i := 0; i < len(f); i++ { - f[i] = Frame((*s)[i]) - } - return f -} - -func callers() *stack { - const depth = 32 - var pcs [depth]uintptr - n := runtime.Callers(3, pcs[:]) - var st stack = pcs[0:n] - return &st -} - -// funcname removes the path prefix component of a function's name reported by func.Name(). -func funcname(name string) string { - i := strings.LastIndex(name, "/") - name = name[i+1:] - i = strings.Index(name, ".") - return name[i+1:] -} diff --git a/vendor/modules.txt b/vendor/modules.txt index a7d148b7..997ee916 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -19,14 +19,14 @@ github.com/go-jose/go-jose/v4/json # github.com/google/uuid v1.1.1 ## explicit github.com/google/uuid -# github.com/gorilla/csrf v1.6.0 -## explicit +# github.com/gorilla/csrf v1.7.3 +## explicit; go 1.20 github.com/gorilla/csrf # github.com/gorilla/mux v1.7.3 ## explicit github.com/gorilla/mux -# github.com/gorilla/securecookie v1.1.1 -## explicit +# github.com/gorilla/securecookie v1.1.2 +## explicit; go 1.20 github.com/gorilla/securecookie # github.com/gorilla/sessions v1.2.0 ## explicit @@ -54,9 +54,6 @@ github.com/libvirt/libvirt-go # github.com/libvirt/libvirt-go-xml v7.4.0+incompatible ## explicit github.com/libvirt/libvirt-go-xml -# github.com/pkg/errors v0.8.1 -## explicit -github.com/pkg/errors # github.com/rs/zerolog v1.15.0 ## explicit github.com/rs/zerolog