diff --git a/go.mod b/go.mod index 721a60e..2f7301b 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,22 @@ module github.com/StiviiK/keycloak-traefik-forward-auth -go 1.24 +go 1.21 -toolchain go1.24.2 +toolchain go1.22.2 require ( github.com/caarlos0/env v3.5.0+incompatible - github.com/coreos/go-oidc/v3 v3.14.1 + github.com/coreos/go-oidc/v3 v3.12.0 github.com/google/uuid v1.6.0 github.com/sirupsen/logrus v1.9.3 - golang.org/x/oauth2 v0.30.0 + golang.org/x/oauth2 v0.25.0 ) -require ( - github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 // indirect - github.com/pzentenoe/go-cache v1.0.0 // indirect -) +require github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 // indirect require ( - github.com/go-jose/go-jose/v4 v4.1.0 // indirect - github.com/tg123/go-htpasswd v1.2.4 - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/sys v0.33.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect + github.com/tg123/go-htpasswd v1.2.3 + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sys v0.29.0 // indirect ) diff --git a/go.sum b/go.sum index e30aad2..98a24d3 100644 --- a/go.sum +++ b/go.sum @@ -1,37 +1,35 @@ -github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI= -github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= -github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= -github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= +github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= +github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pzentenoe/go-cache v1.0.0 h1:6jHsrh4CGKSBBmvNrEDn+EN9cJd4qOqLsHb7xWWEPBM= -github.com/pzentenoe/go-cache v1.0.0/go.mod h1:1JaNc73+p1tmcbNJwK55vtPR40h0hIoqqjlnhBZevBw= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tg123/go-htpasswd v1.2.4 h1:HgH8KKCjdmo7jjXWN9k1nefPBd7Be3tFCTjc2jPraPU= -github.com/tg123/go-htpasswd v1.2.4/go.mod h1:EKThQok9xHkun6NBMynNv6Jmu24A33XdZzzl4Q7H1+0= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tg123/go-htpasswd v1.2.3 h1:ALR6ZBIc2m9u70m+eAWUFt5p43ISbIvAvRFYzZPTOY8= +github.com/tg123/go-htpasswd v1.2.3/go.mod h1:FcIrK0J+6zptgVwK1JDlqyajW/1B4PtuJ/FLWl7nx8A= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/forwardauth/auth.go b/pkg/forwardauth/auth.go index 1d1d98a..13e38b5 100644 --- a/pkg/forwardauth/auth.go +++ b/pkg/forwardauth/auth.go @@ -44,22 +44,15 @@ func (fw *ForwardAuth) IsAuthenticated(context context.Context, logger *logrus.E var claims Claims logger = logger.WithField("FunctionSource", "IsAuthenticated") - // Check if we have a session cookie - cookie, err := fw.GetSessionCookie(r, options) + // Check if we have an Auth cookie + cookie, err := fw.GetAuthCookie(r) if err != nil { logger.Error(err.Error()) return &claims, err } - sessionId := cookie.Value - session := fw.SessionCache.Get(sessionId) - if session == nil { - err = errors.New("session not found") - return &claims, err - } - // check if the token is valid - idToken, err := fw.OidcVefifier.Verify(context, session.IDToken) + idToken, err := fw.OidcVefifier.Verify(context, cookie.Value) switch { case err == nil: // Token is valid @@ -72,24 +65,36 @@ func (fw *ForwardAuth) IsAuthenticated(context context.Context, logger *logrus.E } return &claims, nil + + // Todo: Updating the cookies does sadly not work here case strings.Contains(err.Error(), "expired"): // Token is expired logger.Info("Received expired token, trying to refesh it.") - result, err := fw.RefreshToken(context, session.RefreshToken) + refreshCookie, err := fw.GetRefreshAuthCookie(r) if err != nil { - fw.SessionCache.Delete(sessionId) logger.Error(err.Error()) return &claims, err } - newSession := SessionCacheItem{IDToken: result.IDToken, RefreshToken: result.RefreshToken} - fw.SessionCache.Update(sessionId, &newSession) + result, err := fw.RefreshToken(context, refreshCookie.Value) + if err != nil { + logger.Error(err.Error()) + return &claims, err + } + + http.SetCookie(w, fw.MakeAuthCookie(options, result)) + if len(result.RefreshToken) > 0 { // Do we have an refresh token? + http.SetCookie(w, fw.MakeRefreshAuthCookie(options, result)) + } return result.IDTokenClaims, nil - default: // Other error - fw.SessionCache.Delete(sessionId) + case err != nil: // Other error logger.Error(err.Error()) return &claims, err + + default: + logger.Error("default case, should not happen") + return &claims, errors.New("default case") } } diff --git a/pkg/forwardauth/cookies.go b/pkg/forwardauth/cookies.go index 5ef7715..f38647b 100644 --- a/pkg/forwardauth/cookies.go +++ b/pkg/forwardauth/cookies.go @@ -26,15 +26,15 @@ func getBaseCookie(options *options.Options) *http.Cookie { func (fw *ForwardAuth) MakeCSRFCookie(w http.ResponseWriter, r *http.Request, options *options.Options, state string) *http.Cookie { cookie := getBaseCookie(options) - cookie.Name = options.CookiePrefix + "csrf" + cookie.Name = "__auth_csrf" cookie.Value = fmt.Sprintf("%s|%s", fw.GetReturnUri(r), state) cookie.Expires = time.Now().Local().Add(time.Hour) return cookie } -func (fw *ForwardAuth) ValidateCSRFCookie(r *http.Request, options *options.Options) (state string, redirect string, error error) { - csrfCookie, err := r.Cookie(options.CookiePrefix + "csrf") +func (fw *ForwardAuth) ValidateCSRFCookie(r *http.Request) (state string, redirect string, error error) { + csrfCookie, err := r.Cookie("__auth_csrf") if err != nil { return "", "", errors.New("Missing csrf cookie") } @@ -58,28 +58,49 @@ func (fw *ForwardAuth) ValidateCSRFCookie(r *http.Request, options *options.Opti func (fw *ForwardAuth) ClearCSRFCookie(options *options.Options) *http.Cookie { cookie := getBaseCookie(options) - cookie.Name = options.CookiePrefix + "csrf" + cookie.Name = "__auth_csrf" cookie.Expires = time.Now().Local().Add(time.Hour * -1) return cookie } -func (fw *ForwardAuth) MakeSessionCookie(options *options.Options, sessionId string) *http.Cookie { +func (fw *ForwardAuth) MakeAuthCookie(options *options.Options, authResult *AuthenticatationResult) *http.Cookie { cookie := getBaseCookie(options) - cookie.Name = options.CookiePrefix + "session" - cookie.Value = sessionId - cookie.Expires = time.Now().Local().Add(time.Hour * time.Duration(options.SessionLifetime)) + cookie.Name = "__auth" + cookie.Value = authResult.IDToken + cookie.Expires = time.Now().Local().Add(time.Hour * 24) return cookie } -func (fw *ForwardAuth) GetSessionCookie(r *http.Request, options *options.Options) (*http.Cookie, error) { - return r.Cookie(options.CookiePrefix + "session") +func (fw *ForwardAuth) GetAuthCookie(r *http.Request) (*http.Cookie, error) { + return r.Cookie("__auth") } -func (fw *ForwardAuth) ClearSessionCookie(options *options.Options) *http.Cookie { +func (fw *ForwardAuth) ClearAuthCookie(options *options.Options) *http.Cookie { cookie := getBaseCookie(options) - cookie.Name = options.CookiePrefix + "session" + cookie.Name = "__auth" + cookie.Expires = time.Now().Local().Add(time.Hour * -1) + + return cookie +} + +func (fw *ForwardAuth) MakeRefreshAuthCookie(options *options.Options, authResult *AuthenticatationResult) *http.Cookie { + cookie := getBaseCookie(options) + cookie.Name = "__auth_refresh" + cookie.Value = authResult.RefreshToken + cookie.Expires = time.Now().Local().Add(time.Hour * 24) + + return cookie +} + +func (fw *ForwardAuth) GetRefreshAuthCookie(r *http.Request) (*http.Cookie, error) { + return r.Cookie("__auth_refresh") +} + +func (fw *ForwardAuth) ClearRefreshAuthCookie(options *options.Options) *http.Cookie { + cookie := getBaseCookie(options) + cookie.Name = "__auth_refresh" cookie.Expires = time.Now().Local().Add(time.Hour * -1) return cookie diff --git a/pkg/forwardauth/forwardauth.go b/pkg/forwardauth/forwardauth.go index 3177a9e..b10c563 100644 --- a/pkg/forwardauth/forwardauth.go +++ b/pkg/forwardauth/forwardauth.go @@ -20,7 +20,6 @@ type ForwardAuth struct { OidcProvider *oidc.Provider OAuth2Config oauth2.Config OidcVefifier *oidc.IDTokenVerifier - SessionCache SessionCache } // Claims represents the claims struct which we get from the identity provider @@ -69,6 +68,5 @@ func Create(ctx context.Context, options *options.Options) (*ForwardAuth, error) Scopes: scopes, }, OidcVefifier: verifier, - SessionCache: newSessionCache(options), }, nil } diff --git a/pkg/forwardauth/session.go b/pkg/forwardauth/session.go deleted file mode 100644 index 28f75e9..0000000 --- a/pkg/forwardauth/session.go +++ /dev/null @@ -1,51 +0,0 @@ -package forwardauth - -import ( - "time" - - "github.com/StiviiK/keycloak-traefik-forward-auth/pkg/options" - "github.com/google/uuid" - "github.com/pzentenoe/go-cache" -) - -type SessionCache struct { - internal *cache.Cache -} - -type SessionCacheItem struct { - IDToken string - RefreshToken string -} - -func newSessionCache(options *options.Options) SessionCache { - return SessionCache{ - internal: cache.New(time.Hour*time.Duration(options.SessionLifetime), time.Hour), - } -} - -func (c *SessionCache) Get(sessionId string) *SessionCacheItem { - itm, _ := c.internal.Get(sessionId) - if itm == nil { - return nil - } - return itm.(*SessionCacheItem) -} - -func (c *SessionCache) Create(session *SessionCacheItem) string { - sessionId := uuid.New().String() - c.internal.SetDefault(sessionId, session) - return sessionId -} - -func (c *SessionCache) Update(sessionId string, session *SessionCacheItem) { - _, exp, found := c.internal.GetWithExpiration(sessionId) - if found { - c.internal.Set(sessionId, session, exp.Sub(time.Now())) - } else { - c.internal.SetDefault(sessionId, session) - } -} - -func (c *SessionCache) Delete(sessionId string) { - c.internal.Delete(sessionId) -} diff --git a/pkg/httphandler/callback.go b/pkg/httphandler/callback.go index 78ae503..9585ac9 100644 --- a/pkg/httphandler/callback.go +++ b/pkg/httphandler/callback.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" - "github.com/StiviiK/keycloak-traefik-forward-auth/pkg/forwardauth" "github.com/sirupsen/logrus" ) @@ -20,7 +19,7 @@ func (root *HttpHandler) callbackHandler(w http.ResponseWriter, r *http.Request, }) // check for the csrf cookie - state, redirect, err := root.forwardAuth.ValidateCSRFCookie(r, root.options) + state, redirect, err := root.forwardAuth.ValidateCSRFCookie(r) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return @@ -42,9 +41,9 @@ func (root *HttpHandler) callbackHandler(w http.ResponseWriter, r *http.Request, // clear the csrf cookie http.SetCookie(w, root.forwardAuth.ClearCSRFCookie(root.options)) - newSession := forwardauth.SessionCacheItem{IDToken: authResult.IDToken, RefreshToken: authResult.RefreshToken} - sessionId := root.forwardAuth.SessionCache.Create(&newSession) - - http.SetCookie(w, root.forwardAuth.MakeSessionCookie(root.options, sessionId)) + http.SetCookie(w, root.forwardAuth.MakeAuthCookie(root.options, authResult)) + //if len(authResult.RefreshToken) > 0 { // Do we have an refresh token? + // http.SetCookie(w, root.forwardAuth.MakeRefreshAuthCookie(root.options, authResult)) + //} http.Redirect(w, r, redirect, http.StatusTemporaryRedirect) } diff --git a/pkg/httphandler/root.go b/pkg/httphandler/root.go index 736b47f..f3d4767 100644 --- a/pkg/httphandler/root.go +++ b/pkg/httphandler/root.go @@ -38,7 +38,8 @@ func (root *HttpHandler) rootHandler(w http.ResponseWriter, r *http.Request, for logger = logger.WithField("FunctionSource", "RootHandler") logger.Warn("IsAuthenticated failed, initating login flow.") - http.SetCookie(w, root.forwardAuth.ClearSessionCookie(root.options)) + http.SetCookie(w, root.forwardAuth.ClearAuthCookie(root.options)) + //http.SetCookie(w, root.forwardAuth.ClearRefreshAuthCookie(root.options)) state := uuid.New().String() http.SetCookie(w, root.forwardAuth.MakeCSRFCookie(w, r, root.options, state)) diff --git a/pkg/options/options.go b/pkg/options/options.go index 39b1172..3a1477d 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -13,19 +13,17 @@ import ( ) type Options struct { - Issuer string `env:"ISSUER"` - ClientID string `env:"CLIENT_ID"` - ClientSecret string `env:"CLIENT_SECRET"` - AuthDomain string `env:"AUTH_DOMAIN"` - CookieDomain string `env:"COOKIE_DOMAIN"` - CookiePrefix string `env:"COOKIE_PREFIX" envDefault:"oidca_"` - Port int `env:"PORT" envDefault:"4181"` - RedirectURL string `env:"REDIRECT_URL" envDefault:"/auth/resp"` - Scopes string `env:"SCOPES"` - BypassUser string `env:"BYPASS_USER"` - BypassFile string `env:"BYPASS_FILE"` - BypassPwd *htpasswd.File - SessionLifetime int `env:"SESSION_LIFETIME" envDefault:"24"` + Issuer string `env:"ISSUER"` + ClientID string `env:"CLIENT_ID"` + ClientSecret string `env:"CLIENT_SECRET"` + AuthDomain string `env:"AUTH_DOMAIN"` + CookieDomain string `env:"COOKIE_DOMAIN"` + Port int `env:"PORT" envDefault:"4181"` + RedirectURL string `env:"REDIRECT_URL" envDefault:"/auth/resp"` + Scopes string `env:"SCOPES"` + BypassUser string `env:"BYPASS_USER"` + BypassFile string `env:"BYPASS_FILE"` + BypassPwd *htpasswd.File } // LoadOptions parses the environment vars and the options