You've already forked oauth2-proxy
mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-08-08 22:46:33 +02:00
feat(cookie) csrf per request limit (#3134)
* Allow setting maximum number of csrf cookies, deleting the oldest if necessary * Add a test for multiple CSRF cookies to remove the old cookie * Add docs/changelog * If limit is <=0 do not clear Signed-off-by: test <bert@transtrend.com> * Better docs Co-authored-by: Jan Larwig <jan@larwig.com> * direct check of option value Co-authored-by: Jan Larwig <jan@larwig.com> * direct use of option value Co-authored-by: Jan Larwig <jan@larwig.com> * sort based on clock compare vs time compare Co-authored-by: Jan Larwig <jan@larwig.com> * clock.Clock does not implement Compare, fix csrf cookie extraction after rename Signed-off-by: Bert Helderman <bert@transtrend.com> * Linter fix * add method signature documentation and slight formatting Signed-off-by: Jan Larwig <jan@larwig.com> * fix: test case for csrf cookie limit and flag Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Bert Helderman <bert@transtrend.com> Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: test <bert@transtrend.com> Co-authored-by: bh-tt <71650427+bh-tt@users.noreply.github.com>
This commit is contained in:
@ -4,6 +4,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
@ -151,6 +153,48 @@ func (c *csrf) SetCookie(rw http.ResponseWriter, req *http.Request) (*http.Cooki
|
||||
return cookie, nil
|
||||
}
|
||||
|
||||
// ClearExtraCsrfCookies limits the amount of existing CSRF cookies by deleting
|
||||
// an excess of cookies controlled through the option CSRFPerRequestLimit
|
||||
func ClearExtraCsrfCookies(opts *options.Cookie, rw http.ResponseWriter, req *http.Request) {
|
||||
if !opts.CSRFPerRequest || opts.CSRFPerRequestLimit <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cookies := req.Cookies()
|
||||
existingCsrfCookies := []*http.Cookie{}
|
||||
startsWith := fmt.Sprintf("%v_", opts.Name)
|
||||
|
||||
// determine how many csrf cookies we have
|
||||
for _, cookie := range cookies {
|
||||
if strings.HasPrefix(cookie.Name, startsWith) && strings.HasSuffix(cookie.Name, "_csrf") {
|
||||
existingCsrfCookies = append(existingCsrfCookies, cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// short circuit return
|
||||
if len(existingCsrfCookies) <= opts.CSRFPerRequestLimit {
|
||||
return
|
||||
}
|
||||
|
||||
decodedCookies := []*csrf{}
|
||||
for _, cookie := range existingCsrfCookies {
|
||||
decodedCookie, err := decodeCSRFCookie(cookie, opts)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
decodedCookies = append(decodedCookies, decodedCookie)
|
||||
}
|
||||
|
||||
// delete the X oldest cookies
|
||||
slices.SortStableFunc(decodedCookies, func(a, b *csrf) int {
|
||||
return a.time.Now().Compare(b.time.Now())
|
||||
})
|
||||
|
||||
for i := 0; i < len(decodedCookies)-opts.CSRFPerRequestLimit; i++ {
|
||||
decodedCookies[i].ClearCookie(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearCookie removes the CSRF cookie
|
||||
func (c *csrf) ClearCookie(rw http.ResponseWriter, req *http.Request) {
|
||||
http.SetCookie(rw, MakeCookieFromOptions(
|
||||
@ -181,7 +225,7 @@ func (c *csrf) encodeCookie() (string, error) {
|
||||
// decodeCSRFCookie validates the signature then decrypts and decodes a CSRF
|
||||
// cookie into a CSRF struct
|
||||
func decodeCSRFCookie(cookie *http.Cookie, opts *options.Cookie) (*csrf, error) {
|
||||
val, _, ok := encryption.Validate(cookie, opts.Secret, opts.Expire)
|
||||
val, t, ok := encryption.Validate(cookie, opts.Secret, opts.Expire)
|
||||
if !ok {
|
||||
return nil, errors.New("CSRF cookie failed validation")
|
||||
}
|
||||
@ -192,7 +236,9 @@ func decodeCSRFCookie(cookie *http.Cookie, opts *options.Cookie) (*csrf, error)
|
||||
}
|
||||
|
||||
// Valid cookie, Unmarshal the CSRF
|
||||
csrf := &csrf{cookieOpts: opts}
|
||||
clock := clock.Clock{}
|
||||
clock.Set(t)
|
||||
csrf := &csrf{cookieOpts: opts, time: clock}
|
||||
err = msgpack.Unmarshal(decrypted, csrf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling data to CSRF: %v", err)
|
||||
|
Reference in New Issue
Block a user