Skip to content

Commit

Permalink
providers/proxy: rework redirect mechanism
Browse files Browse the repository at this point in the history
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
  • Loading branch information
BeryJu committed Feb 20, 2024
1 parent 06a42df commit ddaa975
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 27 deletions.
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -10,7 +10,7 @@ require (
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-openapi/runtime v0.27.1
github.com/go-openapi/strfmt v0.22.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -111,8 +111,8 @@ github.com/go-openapi/swag v0.22.5 h1:fVS63IE3M0lsuWRzuom3RLwUMVI2peDH01s6M70ugy
github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0=
github.com/go-openapi/validate v0.22.4 h1:5v3jmMyIPKTR8Lv9syBAIRxG6lY0RqeBPB1LKEijzk8=
github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZLPOmqnqTUZ2A=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down
17 changes: 7 additions & 10 deletions internal/outpost/proxyv2/application/oauth.go
@@ -1,13 +1,11 @@
package application

import (
"encoding/base64"
"net/http"
"net/url"
"strings"
"time"

"github.com/gorilla/securecookie"
"goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/proxyv2/constants"
)
Expand Down Expand Up @@ -49,7 +47,6 @@ func (a *Application) checkRedirectParam(r *http.Request) (string, bool) {
}

func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request) {
newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
s, _ := a.sessions.Get(r, a.SessionName())
// Check if we already have a state in the session,
// and if we do we don't do anything here
Expand All @@ -65,17 +62,17 @@ func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request) {
http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(currentState), http.StatusFound)
return
}
rd, ok := a.checkRedirectParam(r)
if ok {
s.Values[constants.SessionRedirect] = rd
a.log.WithField("rd", rd).Trace("Setting redirect")
state, err := a.createState(r)
if err != nil {
a.log.WithError(err).Warning("failed to create state")
return
}
s.Values[constants.SessionOAuthState] = newState
err := s.Save(r, rw)
s.Values[constants.SessionOAuthState] = state
err = s.Save(r, rw)
if err != nil {
a.log.WithError(err).Warning("failed to save session")
}
http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(newState), http.StatusFound)
http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(state), http.StatusFound)
}

func (a *Application) handleAuthCallback(rw http.ResponseWriter, r *http.Request) {
Expand Down
67 changes: 67 additions & 0 deletions internal/outpost/proxyv2/application/oauth_redirect.go
@@ -0,0 +1,67 @@
package application

import (
"encoding/base64"
"fmt"
"net/http"

"github.com/golang-jwt/jwt/v5"
"github.com/gorilla/securecookie"
"goauthentik.io/internal/outpost/proxyv2/constants"
)

type OAuthState struct {
State string `json:"state"`
Redirect string `json:"redirect"`
}

func (oas *OAuthState) GetExpirationTime() (*jwt.NumericDate, error) { return nil, nil }
func (oas *OAuthState) GetIssuedAt() (*jwt.NumericDate, error) { return nil, nil }
func (oas *OAuthState) GetNotBefore() (*jwt.NumericDate, error) { return nil, nil }
func (oas *OAuthState) GetIssuer() (string, error) { return "goauthentik.io/outpost", nil }
func (oas *OAuthState) GetSubject() (string, error) { return oas.State, nil }
func (oas *OAuthState) GetAudience() (jwt.ClaimStrings, error) { return nil, nil }

func (a *Application) createState(r *http.Request) (string, error) {
st := &OAuthState{
State: base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32)),
}
rd, ok := a.checkRedirectParam(r)
if ok {
a.log.WithField("rd", rd).Trace("Setting redirect")
st.Redirect = rd
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, st)
tokenString, err := token.SignedString([]byte(a.proxyConfig.GetCookieSecret()))
if err != nil {
return "", err
}
return tokenString, nil
}

func (a *Application) stateFromRequest(r *http.Request) *OAuthState {
s, err := a.sessions.Get(r, a.SessionName())
if err != nil {
a.log.WithError(err).Trace("failed to get session")
return nil
}
stateJwt, ok := s.Values[constants.SessionOAuthState]
if !ok {
return nil
}
token, err := jwt.Parse(stateJwt.(string), func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return a.proxyConfig.CookieSecret, nil
})
if err != nil {
a.log.WithError(err).Warning("failed to parse state jwt")
return nil
}
if claims, ok := token.Claims.(*OAuthState); ok {
return claims
}
return nil
}
2 changes: 1 addition & 1 deletion internal/outpost/proxyv2/application/test.go
Expand Up @@ -45,11 +45,11 @@ func newTestApplication() *Application {
Name: ak.TestSecret(),
ClientId: api.PtrString(ak.TestSecret()),
ClientSecret: api.PtrString(ak.TestSecret()),
CookieDomain: api.PtrString(""),
CookieSecret: api.PtrString(ak.TestSecret()),
ExternalHost: "https://ext.t.goauthentik.io",
InternalHost: api.PtrString("http://backend"),
InternalHostSslValidation: api.PtrBool(true),
CookieDomain: api.PtrString(""),
Mode: api.PROXYMODE_FORWARD_SINGLE.Ptr(),
SkipPathRegex: api.PtrString("/skip.*"),
BasicAuthEnabled: api.PtrBool(true),
Expand Down
17 changes: 6 additions & 11 deletions internal/outpost/proxyv2/application/utils.go
Expand Up @@ -73,18 +73,13 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
}

func (a *Application) redirect(rw http.ResponseWriter, r *http.Request) {
redirect := a.proxyConfig.ExternalHost
s, _ := a.sessions.Get(r, a.SessionName())
redirectR, ok := s.Values[constants.SessionRedirect]
if ok {
redirect = redirectR.(string)
fallbackRedirect := a.proxyConfig.ExternalHost
state := a.stateFromRequest(r)
if state.Redirect == "" {
state.Redirect = fallbackRedirect
}
rd, ok := a.checkRedirectParam(r)
if ok {
redirect = rd
}
a.log.WithField("redirect", redirect).Trace("final redirect")
http.Redirect(rw, r, redirect, http.StatusFound)
a.log.WithField("redirect", state.Redirect).Trace("final redirect")
http.Redirect(rw, r, state.Redirect, http.StatusFound)
}

// toString Generic to string function, currently supports actual strings and integers
Expand Down
4 changes: 2 additions & 2 deletions internal/outpost/proxyv2/hs256/hs256.go
Expand Up @@ -5,7 +5,7 @@ import (
"encoding/base64"
"strings"

"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v5"
)

type KeySet struct {
Expand All @@ -22,7 +22,7 @@ func NewKeySet(secret string) *KeySet {

func (ks *KeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
parts := strings.Split(jwt, ".")
err := ks.m.Verify(strings.Join(parts[0:2], "."), parts[2], []byte(ks.secret))
err := ks.m.Verify(strings.Join(parts[0:2], "."), []byte(parts[2]), []byte(ks.secret))
if err != nil {
return nil, err
}
Expand Down

0 comments on commit ddaa975

Please sign in to comment.