providers/proxy: different cookie name based on hashed client id (#4666)
This commit is contained in:
		
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							| @ -26,6 +26,7 @@ require ( | |||||||
| 	github.com/sirupsen/logrus v1.9.0 | 	github.com/sirupsen/logrus v1.9.0 | ||||||
| 	github.com/stretchr/testify v1.8.1 | 	github.com/stretchr/testify v1.8.1 | ||||||
| 	goauthentik.io/api/v3 v3.2023012.5 | 	goauthentik.io/api/v3 v3.2023012.5 | ||||||
|  | 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||||
| 	golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b | 	golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b | ||||||
| 	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f | 	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f | ||||||
| 	gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b | 	gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b | ||||||
| @ -71,7 +72,7 @@ require ( | |||||||
| 	go.opentelemetry.io/otel/trace v1.11.1 // indirect | 	go.opentelemetry.io/otel/trace v1.11.1 // indirect | ||||||
| 	golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect | 	golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect | ||||||
| 	golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect | 	golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect | ||||||
| 	golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect | 	golang.org/x/sys v0.1.0 // indirect | ||||||
| 	golang.org/x/text v0.3.7 // indirect | 	golang.org/x/text v0.3.7 // indirect | ||||||
| 	google.golang.org/appengine v1.6.7 // indirect | 	google.golang.org/appengine v1.6.7 // indirect | ||||||
| 	google.golang.org/protobuf v1.28.1 // indirect | 	google.golang.org/protobuf v1.28.1 // indirect | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								go.sum
									
									
									
									
									
								
							| @ -405,6 +405,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 | |||||||
| golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||||
| golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | ||||||
|  | golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab h1:628ME69lBm9C6JY2wXhAph/yjN3jezx1z7BIDLUwxjo= | ||||||
|  | golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= | ||||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||||
| @ -528,6 +530,8 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc | |||||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= | ||||||
| golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= | ||||||
|  | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| @ -587,6 +591,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc | |||||||
| golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||||
| golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||||
| golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064 h1:BmCFkEH4nJrYcAc2L08yX5RhYGD4j58PTMkEUDkpz2I= | golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064 h1:BmCFkEH4nJrYcAc2L08yX5RhYGD4j58PTMkEUDkpz2I= | ||||||
|  | golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= | ||||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ package application | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"crypto/sha256" | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
| 	"encoding/gob" | 	"encoding/gob" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @ -40,6 +41,7 @@ type Application struct { | |||||||
| 	oauthConfig   oauth2.Config | 	oauthConfig   oauth2.Config | ||||||
| 	tokenVerifier *oidc.IDTokenVerifier | 	tokenVerifier *oidc.IDTokenVerifier | ||||||
| 	outpostName   string | 	outpostName   string | ||||||
|  | 	sessionName   string | ||||||
|  |  | ||||||
| 	sessions    sessions.Store | 	sessions    sessions.Store | ||||||
| 	proxyConfig api.ProxyOutpostConfig | 	proxyConfig api.ProxyOutpostConfig | ||||||
| @ -48,12 +50,19 @@ type Application struct { | |||||||
| 	log *log.Entry | 	log *log.Entry | ||||||
| 	mux *mux.Router | 	mux *mux.Router | ||||||
| 	ak  *ak.APIController | 	ak  *ak.APIController | ||||||
|  | 	srv Server | ||||||
|  |  | ||||||
| 	errorTemplates  *template.Template | 	errorTemplates  *template.Template | ||||||
| 	authHeaderCache *ttlcache.Cache[string, Claims] | 	authHeaderCache *ttlcache.Cache[string, Claims] | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore, ak *ak.APIController) (*Application, error) { | type Server interface { | ||||||
|  | 	API() *ak.APIController | ||||||
|  | 	Apps() []*Application | ||||||
|  | 	CryptoStore() *ak.CryptoStore | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*Application, error) { | ||||||
| 	gob.Register(Claims{}) | 	gob.Register(Claims{}) | ||||||
| 	muxLogger := log.WithField("logger", "authentik.outpost.proxyv2.application").WithField("name", p.Name) | 	muxLogger := log.WithField("logger", "authentik.outpost.proxyv2.application").WithField("name", p.Name) | ||||||
|  |  | ||||||
| @ -77,13 +86,13 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore | |||||||
| 	}.Encode() | 	}.Encode() | ||||||
|  |  | ||||||
| 	managed := false | 	managed := false | ||||||
| 	if m := ak.Outpost.Managed.Get(); m != nil { | 	if m := server.API().Outpost.Managed.Get(); m != nil { | ||||||
| 		managed = *m == "goauthentik.io/outposts/embedded" | 		managed = *m == "goauthentik.io/outposts/embedded" | ||||||
| 	} | 	} | ||||||
| 	// Configure an OpenID Connect aware OAuth2 client. | 	// Configure an OpenID Connect aware OAuth2 client. | ||||||
| 	endpoint := GetOIDCEndpoint( | 	endpoint := GetOIDCEndpoint( | ||||||
| 		p, | 		p, | ||||||
| 		ak.Outpost.Config["authentik_host"].(string), | 		server.API().Outpost.Config["authentik_host"].(string), | ||||||
| 		managed, | 		managed, | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| @ -100,10 +109,16 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore | |||||||
| 		Scopes:       p.ScopesToRequest, | 		Scopes:       p.ScopesToRequest, | ||||||
| 	} | 	} | ||||||
| 	mux := mux.NewRouter() | 	mux := mux.NewRouter() | ||||||
|  |  | ||||||
|  | 	h := sha256.New() | ||||||
|  | 	bs := string(h.Sum([]byte(*p.ClientId))) | ||||||
|  | 	sessionName := fmt.Sprintf("authentik_proxy_%s", bs[:8]) | ||||||
|  |  | ||||||
| 	a := &Application{ | 	a := &Application{ | ||||||
| 		Host:            externalHost.Host, | 		Host:            externalHost.Host, | ||||||
| 		log:             muxLogger, | 		log:             muxLogger, | ||||||
| 		outpostName:     ak.Outpost.Name, | 		outpostName:     server.API().Outpost.Name, | ||||||
|  | 		sessionName:     sessionName, | ||||||
| 		endpoint:        endpoint, | 		endpoint:        endpoint, | ||||||
| 		oauthConfig:     oauth2Config, | 		oauthConfig:     oauth2Config, | ||||||
| 		tokenVerifier:   verifier, | 		tokenVerifier:   verifier, | ||||||
| @ -111,8 +126,9 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore | |||||||
| 		httpClient:      c, | 		httpClient:      c, | ||||||
| 		mux:             mux, | 		mux:             mux, | ||||||
| 		errorTemplates:  templates.GetTemplates(), | 		errorTemplates:  templates.GetTemplates(), | ||||||
| 		ak:              ak, | 		ak:              server.API(), | ||||||
| 		authHeaderCache: ttlcache.New(ttlcache.WithDisableTouchOnHit[string, Claims]()), | 		authHeaderCache: ttlcache.New(ttlcache.WithDisableTouchOnHit[string, Claims]()), | ||||||
|  | 		srv:             server, | ||||||
| 	} | 	} | ||||||
| 	go a.authHeaderCache.Start() | 	go a.authHeaderCache.Start() | ||||||
| 	a.sessions = a.getStore(p, externalHost) | 	a.sessions = a.getStore(p, externalHost) | ||||||
| @ -153,7 +169,9 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore | |||||||
| 			}).Observe(float64(after)) | 			}).Observe(float64(after)) | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  | 	if server.API().GlobalConfig.ErrorReporting.Enabled { | ||||||
| 		mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) | 		mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) | ||||||
|  | 	} | ||||||
| 	mux.Use(func(inner http.Handler) http.Handler { | 	mux.Use(func(inner http.Handler) http.Handler { | ||||||
| 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
| 			if strings.EqualFold(r.URL.Query().Get(CallbackSignature), "true") { | 			if strings.EqualFold(r.URL.Query().Get(CallbackSignature), "true") { | ||||||
| @ -184,11 +202,11 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if kp := p.Certificate.Get(); kp != nil { | 	if kp := p.Certificate.Get(); kp != nil { | ||||||
| 		err := cs.AddKeypair(*kp) | 		err := server.CryptoStore().AddKeypair(*kp) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, fmt.Errorf("failed to initially fetch certificate: %w", err) | 			return nil, fmt.Errorf("failed to initially fetch certificate: %w", err) | ||||||
| 		} | 		} | ||||||
| 		a.Cert = cs.Get(*kp) | 		a.Cert = server.CryptoStore().Get(*kp) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if *p.SkipPathRegex != "" { | 	if *p.SkipPathRegex != "" { | ||||||
| @ -235,7 +253,7 @@ func (a *Application) Stop() { | |||||||
|  |  | ||||||
| func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) { | func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	redirect := a.endpoint.EndSessionEndpoint | 	redirect := a.endpoint.EndSessionEndpoint | ||||||
| 	s, err := a.sessions.Get(r, constants.SessionName) | 	s, err := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		a.redirectToStart(rw, r) | 		a.redirectToStart(rw, r) | ||||||
| 		return | 		return | ||||||
|  | |||||||
| @ -49,7 +49,7 @@ func (a *Application) checkAuth(rw http.ResponseWriter, r *http.Request) (*Claim | |||||||
| } | } | ||||||
|  |  | ||||||
| func (a *Application) getClaimsFromSession(r *http.Request) *Claims { | func (a *Application) getClaimsFromSession(r *http.Request) *Claims { | ||||||
| 	s, err := a.sessions.Get(r, constants.SessionName) | 	s, err := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		// err == user has no session/session is not valid, reject | 		// err == user has no session/session is not valid, reject | ||||||
| 		return nil | 		return nil | ||||||
| @ -77,7 +77,7 @@ func (a *Application) getClaimsFromCache(r *http.Request) *Claims { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (a *Application) saveAndCacheClaims(rw http.ResponseWriter, r *http.Request, claims Claims) (*Claims, error) { | func (a *Application) saveAndCacheClaims(rw http.ResponseWriter, r *http.Request, claims Claims) (*Claims, error) { | ||||||
| 	s, _ := a.sessions.Get(r, constants.SessionName) | 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||||
|  |  | ||||||
| 	s.Values[constants.SessionClaims] = claims | 	s.Values[constants.SessionClaims] = claims | ||||||
| 	err := s.Save(r, rw) | 	err := s.Save(r, rw) | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ func (a *Application) ErrorPage(rw http.ResponseWriter, r *http.Request, err str | |||||||
| 		Message:     "Error proxying to upstream server", | 		Message:     "Error proxying to upstream server", | ||||||
| 		ProxyPrefix: "/outpost.goauthentik.io", | 		ProxyPrefix: "/outpost.goauthentik.io", | ||||||
| 	} | 	} | ||||||
| 	if claims != nil && claims.Proxy.IsSuperuser { | 	if claims != nil && claims.Proxy != nil && claims.Proxy.IsSuperuser { | ||||||
| 		data.Message = err | 		data.Message = err | ||||||
| 	} else { | 	} else { | ||||||
| 		data.Message = "Failed to connect to backend." | 		data.Message = "Failed to connect to backend." | ||||||
|  | |||||||
| @ -64,7 +64,7 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque | |||||||
| 	// to a (possibly) different domain, but we want to be redirected back | 	// to a (possibly) different domain, but we want to be redirected back | ||||||
| 	// to the application | 	// to the application | ||||||
| 	// X-Forwarded-Uri is only the path, so we need to build the entire URL | 	// X-Forwarded-Uri is only the path, so we need to build the entire URL | ||||||
| 	s, _ := a.sessions.Get(r, constants.SessionName) | 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | ||||||
| 		s.Values[constants.SessionRedirect] = fwd.String() | 		s.Values[constants.SessionRedirect] = fwd.String() | ||||||
| 		err = s.Save(r, rw) | 		err = s.Save(r, rw) | ||||||
| @ -115,7 +115,7 @@ func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request | |||||||
| 	// to a (possibly) different domain, but we want to be redirected back | 	// to a (possibly) different domain, but we want to be redirected back | ||||||
| 	// to the application | 	// to the application | ||||||
| 	// X-Forwarded-Uri is only the path, so we need to build the entire URL | 	// X-Forwarded-Uri is only the path, so we need to build the entire URL | ||||||
| 	s, _ := a.sessions.Get(r, constants.SessionName) | 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | ||||||
| 		s.Values[constants.SessionRedirect] = fwd.String() | 		s.Values[constants.SessionRedirect] = fwd.String() | ||||||
| 		err = s.Save(r, rw) | 		err = s.Save(r, rw) | ||||||
| @ -151,7 +151,7 @@ func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(r, constants.SessionName) | 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | ||||||
| 		s.Values[constants.SessionRedirect] = fwd.String() | 		s.Values[constants.SessionRedirect] = fwd.String() | ||||||
| 		err = s.Save(r, rw) | 		err = s.Save(r, rw) | ||||||
| @ -190,7 +190,7 @@ func (a *Application) forwardHandleEnvoy(rw http.ResponseWriter, r *http.Request | |||||||
| 	// to a (possibly) different domain, but we want to be redirected back | 	// to a (possibly) different domain, but we want to be redirected back | ||||||
| 	// to the application | 	// to the application | ||||||
| 	// X-Forwarded-Uri is only the path, so we need to build the entire URL | 	// X-Forwarded-Uri is only the path, so we need to build the entire URL | ||||||
| 	s, _ := a.sessions.Get(r, constants.SessionName) | 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | 	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet { | ||||||
| 		s.Values[constants.SessionRedirect] = fwd.String() | 		s.Values[constants.SessionRedirect] = fwd.String() | ||||||
| 		err = s.Save(r, rw) | 		err = s.Save(r, rw) | ||||||
|  | |||||||
| @ -48,7 +48,7 @@ func TestForwardHandleCaddy_Single_Headers(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	shouldUrl := url.Values{ | 	shouldUrl := url.Values{ | ||||||
| 		"client_id":     []string{*a.proxyConfig.ClientId}, | 		"client_id":     []string{*a.proxyConfig.ClientId}, | ||||||
| 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | ||||||
| @ -69,7 +69,7 @@ func TestForwardHandleCaddy_Single_Claims(t *testing.T) { | |||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleCaddy(rr, req) | 	a.forwardHandleCaddy(rr, req) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s.ID = uuid.New().String() | 	s.ID = uuid.New().String() | ||||||
| 	s.Options.MaxAge = 86400 | 	s.Options.MaxAge = 86400 | ||||||
| 	s.Values[constants.SessionClaims] = Claims{ | 	s.Values[constants.SessionClaims] = Claims{ | ||||||
| @ -135,7 +135,7 @@ func TestForwardHandleCaddy_Domain_Header(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	shouldUrl := url.Values{ | 	shouldUrl := url.Values{ | ||||||
| 		"client_id":     []string{*a.proxyConfig.ClientId}, | 		"client_id":     []string{*a.proxyConfig.ClientId}, | ||||||
| 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ func TestForwardHandleEnvoy_Single_Headers(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	shouldUrl := url.Values{ | 	shouldUrl := url.Values{ | ||||||
| 		"client_id":     []string{*a.proxyConfig.ClientId}, | 		"client_id":     []string{*a.proxyConfig.ClientId}, | ||||||
| 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | ||||||
| @ -51,7 +51,7 @@ func TestForwardHandleEnvoy_Single_Claims(t *testing.T) { | |||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleEnvoy(rr, req) | 	a.forwardHandleEnvoy(rr, req) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s.ID = uuid.New().String() | 	s.ID = uuid.New().String() | ||||||
| 	s.Options.MaxAge = 86400 | 	s.Options.MaxAge = 86400 | ||||||
| 	s.Values[constants.SessionClaims] = Claims{ | 	s.Values[constants.SessionClaims] = Claims{ | ||||||
| @ -103,7 +103,7 @@ func TestForwardHandleEnvoy_Domain_Header(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
|  |  | ||||||
| 	shouldUrl := url.Values{ | 	shouldUrl := url.Values{ | ||||||
| 		"client_id":     []string{*a.proxyConfig.ClientId}, | 		"client_id":     []string{*a.proxyConfig.ClientId}, | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusUnauthorized, rr.Code) | 	assert.Equal(t, http.StatusUnauthorized, rr.Code) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -56,7 +56,7 @@ func TestForwardHandleNginx_Single_URI(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusUnauthorized, rr.Code) | 	assert.Equal(t, http.StatusUnauthorized, rr.Code) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	assert.Equal(t, "/app", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "/app", s.Values[constants.SessionRedirect]) | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -68,7 +68,7 @@ func TestForwardHandleNginx_Single_Claims(t *testing.T) { | |||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleNginx(rr, req) | 	a.forwardHandleNginx(rr, req) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s.ID = uuid.New().String() | 	s.ID = uuid.New().String() | ||||||
| 	s.Options.MaxAge = 86400 | 	s.Options.MaxAge = 86400 | ||||||
| 	s.Values[constants.SessionClaims] = Claims{ | 	s.Values[constants.SessionClaims] = Claims{ | ||||||
| @ -132,6 +132,6 @@ func TestForwardHandleNginx_Domain_Header(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusUnauthorized, rr.Code) | 	assert.Equal(t, http.StatusUnauthorized, rr.Code) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | ||||||
| } | } | ||||||
|  | |||||||
| @ -48,7 +48,7 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	shouldUrl := url.Values{ | 	shouldUrl := url.Values{ | ||||||
| 		"client_id":     []string{*a.proxyConfig.ClientId}, | 		"client_id":     []string{*a.proxyConfig.ClientId}, | ||||||
| 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | ||||||
| @ -69,7 +69,7 @@ func TestForwardHandleTraefik_Single_Claims(t *testing.T) { | |||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleTraefik(rr, req) | 	a.forwardHandleTraefik(rr, req) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s.ID = uuid.New().String() | 	s.ID = uuid.New().String() | ||||||
| 	s.Options.MaxAge = 86400 | 	s.Options.MaxAge = 86400 | ||||||
| 	s.Values[constants.SessionClaims] = Claims{ | 	s.Values[constants.SessionClaims] = Claims{ | ||||||
| @ -135,7 +135,7 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	shouldUrl := url.Values{ | 	shouldUrl := url.Values{ | ||||||
| 		"client_id":     []string{*a.proxyConfig.ClientId}, | 		"client_id":     []string{*a.proxyConfig.ClientId}, | ||||||
| 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"}, | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ func TestProxy_ModifyRequest_Claims(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s.ID = uuid.New().String() | 	s.ID = uuid.New().String() | ||||||
| 	s.Options.MaxAge = 86400 | 	s.Options.MaxAge = 86400 | ||||||
| 	s.Values[constants.SessionClaims] = Claims{ | 	s.Values[constants.SessionClaims] = Claims{ | ||||||
| @ -100,7 +100,7 @@ func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s.ID = uuid.New().String() | 	s.ID = uuid.New().String() | ||||||
| 	s.Options.MaxAge = 86400 | 	s.Options.MaxAge = 86400 | ||||||
| 	s.Values[constants.SessionClaims] = Claims{ | 	s.Values[constants.SessionClaims] = Claims{ | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ func (a *Application) checkRedirectParam(r *http.Request) (string, bool) { | |||||||
|  |  | ||||||
| func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request) { | func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) | 	newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) | ||||||
| 	s, _ := a.sessions.Get(r, constants.SessionName) | 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||||
| 	// Check if we already have a state in the session, | 	// Check if we already have a state in the session, | ||||||
| 	// and if we do we don't do anything here | 	// and if we do we don't do anything here | ||||||
| 	currentState, ok := s.Values[constants.SessionOAuthState].(string) | 	currentState, ok := s.Values[constants.SessionOAuthState].(string) | ||||||
| @ -74,7 +74,7 @@ func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (a *Application) handleAuthCallback(rw http.ResponseWriter, r *http.Request) { | func (a *Application) handleAuthCallback(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	s, err := a.sessions.Get(r, constants.SessionName) | 	s, err := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		a.log.WithError(err).Trace("failed to get session") | 		a.log.WithError(err).Trace("failed to get session") | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ import ( | |||||||
| 	"github.com/gorilla/sessions" | 	"github.com/gorilla/sessions" | ||||||
| 	"goauthentik.io/api/v3" | 	"goauthentik.io/api/v3" | ||||||
| 	"goauthentik.io/internal/config" | 	"goauthentik.io/internal/config" | ||||||
|  | 	"goauthentik.io/internal/outpost/proxyv2/codecs" | ||||||
| 	"goauthentik.io/internal/outpost/proxyv2/constants" | 	"goauthentik.io/internal/outpost/proxyv2/constants" | ||||||
| 	"gopkg.in/boj/redistore.v1" | 	"gopkg.in/boj/redistore.v1" | ||||||
| ) | ) | ||||||
| @ -21,27 +22,28 @@ import ( | |||||||
| const RedisKeyPrefix = "authentik_proxy_session_" | const RedisKeyPrefix = "authentik_proxy_session_" | ||||||
|  |  | ||||||
| func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) sessions.Store { | func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) sessions.Store { | ||||||
| 	var store sessions.Store | 	maxAge := 0 | ||||||
| 	if config.Get().Redis.Host != "" { |  | ||||||
| 		rs, err := redistore.NewRediStoreWithDB(10, "tcp", fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), config.Get().Redis.Password, strconv.Itoa(config.Get().Redis.DB), []byte(*p.CookieSecret)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			panic(err) |  | ||||||
| 		} |  | ||||||
| 		rs.SetMaxLength(math.MaxInt) |  | ||||||
| 		rs.SetKeyPrefix(RedisKeyPrefix) |  | ||||||
| 	if p.AccessTokenValidity.IsSet() { | 	if p.AccessTokenValidity.IsSet() { | ||||||
| 		t := p.AccessTokenValidity.Get() | 		t := p.AccessTokenValidity.Get() | ||||||
| 		// Add one to the validity to ensure we don't have a session with indefinite length | 		// Add one to the validity to ensure we don't have a session with indefinite length | ||||||
| 			rs.SetMaxAge(int(*t) + 1) | 		maxAge = int(*t) + 1 | ||||||
| 		} else { |  | ||||||
| 			rs.SetMaxAge(0) |  | ||||||
| 	} | 	} | ||||||
|  | 	if config.Get().Redis.Host == "foo" { | ||||||
|  | 		rs, err := redistore.NewRediStoreWithDB(10, "tcp", fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), config.Get().Redis.Password, strconv.Itoa(config.Get().Redis.DB)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 		rs.Codecs = codecs.CodecsFromPairs(maxAge, []byte(*p.CookieSecret)) | ||||||
|  | 		rs.SetMaxLength(math.MaxInt) | ||||||
|  | 		rs.SetKeyPrefix(RedisKeyPrefix) | ||||||
|  |  | ||||||
| 		rs.Options.Domain = *p.CookieDomain | 		rs.Options.Domain = *p.CookieDomain | ||||||
| 		a.log.Trace("using redis session backend") | 		a.log.Trace("using redis session backend") | ||||||
| 		store = rs | 		return rs | ||||||
| 	} else { | 	} | ||||||
| 	dir := os.TempDir() | 	dir := os.TempDir() | ||||||
| 		cs := sessions.NewFilesystemStore(dir, []byte(*p.CookieSecret)) | 	cs := sessions.NewFilesystemStore(dir) | ||||||
|  | 	cs.Codecs = codecs.CodecsFromPairs(maxAge, []byte(*p.CookieSecret)) | ||||||
| 	// https://github.com/markbates/goth/commit/7276be0fdf719ddff753f3574ef0f967e4a5a5f7 | 	// https://github.com/markbates/goth/commit/7276be0fdf719ddff753f3574ef0f967e4a5a5f7 | ||||||
| 	// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with: | 	// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with: | ||||||
| 	// securecookie: the value is too long | 	// securecookie: the value is too long | ||||||
| @ -49,22 +51,26 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | |||||||
|  |  | ||||||
| 	// Note, when using the FilesystemStore only the session.ID is written to a browser cookie, so this is explicit for the storage on disk | 	// Note, when using the FilesystemStore only the session.ID is written to a browser cookie, so this is explicit for the storage on disk | ||||||
| 	cs.MaxLength(math.MaxInt) | 	cs.MaxLength(math.MaxInt) | ||||||
| 		if p.AccessTokenValidity.IsSet() { |  | ||||||
| 			t := p.AccessTokenValidity.Get() |  | ||||||
| 			// Add one to the validity to ensure we don't have a session with indefinite length |  | ||||||
| 			cs.MaxAge(int(*t) + 1) |  | ||||||
| 		} else { |  | ||||||
| 			cs.MaxAge(0) |  | ||||||
| 		} |  | ||||||
| 	cs.Options.Domain = *p.CookieDomain | 	cs.Options.Domain = *p.CookieDomain | ||||||
| 	a.log.WithField("dir", dir).Trace("using filesystem session backend") | 	a.log.WithField("dir", dir).Trace("using filesystem session backend") | ||||||
| 		store = cs | 	return cs | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Application) SessionName() string { | ||||||
|  | 	return a.sessionName | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Application) getAllCodecs() []securecookie.Codec { | ||||||
|  | 	apps := a.srv.Apps() | ||||||
|  | 	cs := []securecookie.Codec{} | ||||||
|  | 	for _, app := range apps { | ||||||
|  | 		cs = append(cs, codecs.CodecsFromPairs(0, []byte(*app.proxyConfig.CookieSecret))...) | ||||||
| 	} | 	} | ||||||
| 	return store | 	return cs | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *Application) Logout(sub string) error { | func (a *Application) Logout(sub string) error { | ||||||
| 	if fs, ok := a.sessions.(*sessions.FilesystemStore); ok { | 	if _, ok := a.sessions.(*sessions.FilesystemStore); ok { | ||||||
| 		files, err := os.ReadDir(os.TempDir()) | 		files, err := os.ReadDir(os.TempDir()) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @ -81,8 +87,8 @@ func (a *Application) Logout(sub string) error { | |||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			err = securecookie.DecodeMulti( | 			err = securecookie.DecodeMulti( | ||||||
| 				constants.SessionName, string(data), | 				a.SessionName(), string(data), | ||||||
| 				&s.Values, fs.Codecs..., | 				&s.Values, a.getAllCodecs()..., | ||||||
| 			) | 			) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				a.log.WithError(err).Trace("failed to decode session") | 				a.log.WithError(err).Trace("failed to decode session") | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ func TestLogout(t *testing.T) { | |||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
|  |  | ||||||
| 	// Login once | 	// Login once | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s.ID = uuid.New().String() | 	s.ID = uuid.New().String() | ||||||
| 	s.Options.MaxAge = 86400 | 	s.Options.MaxAge = 86400 | ||||||
| 	s.Values[constants.SessionClaims] = Claims{ | 	s.Values[constants.SessionClaims] = Claims{ | ||||||
| @ -36,7 +36,7 @@ func TestLogout(t *testing.T) { | |||||||
| 	assert.Equal(t, http.StatusBadGateway, rr.Code) | 	assert.Equal(t, http.StatusBadGateway, rr.Code) | ||||||
|  |  | ||||||
| 	// Login twice | 	// Login twice | ||||||
| 	s2, _ := a.sessions.Get(req, constants.SessionName) | 	s2, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s2.ID = uuid.New().String() | 	s2.ID = uuid.New().String() | ||||||
| 	s2.Options.MaxAge = 86400 | 	s2.Options.MaxAge = 86400 | ||||||
| 	s2.Values[constants.SessionClaims] = Claims{ | 	s2.Values[constants.SessionClaims] = Claims{ | ||||||
| @ -53,7 +53,7 @@ func TestLogout(t *testing.T) { | |||||||
|  |  | ||||||
| 	// Logout | 	// Logout | ||||||
| 	req, _ = http.NewRequest("GET", "https://ext.t.goauthentik.io/outpost.goauthentik.io/sign_out", nil) | 	req, _ = http.NewRequest("GET", "https://ext.t.goauthentik.io/outpost.goauthentik.io/sign_out", nil) | ||||||
| 	s3, _ := a.sessions.Get(req, constants.SessionName) | 	s3, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	s3.ID = uuid.New().String() | 	s3.ID = uuid.New().String() | ||||||
| 	s3.Options.MaxAge = 86400 | 	s3.Options.MaxAge = 86400 | ||||||
| 	s3.Values[constants.SessionClaims] = Claims{ | 	s3.Values[constants.SessionClaims] = Claims{ | ||||||
|  | |||||||
| @ -7,7 +7,39 @@ import ( | |||||||
| 	"goauthentik.io/internal/outpost/ak" | 	"goauthentik.io/internal/outpost/ak" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | type testServer struct { | ||||||
|  | 	api  *ak.APIController | ||||||
|  | 	apps []*Application | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newTestServer() *testServer { | ||||||
|  | 	return &testServer{ | ||||||
|  | 		api: ak.MockAK( | ||||||
|  | 			api.Outpost{ | ||||||
|  | 				Config: map[string]interface{}{ | ||||||
|  | 					"authentik_host": ak.TestSecret(), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			ak.MockConfig(), | ||||||
|  | 		), | ||||||
|  | 		apps: make([]*Application, 0), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ts *testServer) API() *ak.APIController { | ||||||
|  | 	return ts.api | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ts *testServer) CryptoStore() *ak.CryptoStore { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ts *testServer) Apps() []*Application { | ||||||
|  | 	return ts.apps | ||||||
|  | } | ||||||
|  |  | ||||||
| func newTestApplication() *Application { | func newTestApplication() *Application { | ||||||
|  | 	ts := newTestServer() | ||||||
| 	a, _ := NewApplication( | 	a, _ := NewApplication( | ||||||
| 		api.ProxyOutpostConfig{ | 		api.ProxyOutpostConfig{ | ||||||
| 			Name:                       ak.TestSecret(), | 			Name:                       ak.TestSecret(), | ||||||
| @ -30,15 +62,8 @@ func newTestApplication() *Application { | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		http.DefaultClient, | 		http.DefaultClient, | ||||||
| 		nil, | 		ts, | ||||||
| 		ak.MockAK( |  | ||||||
| 			api.Outpost{ |  | ||||||
| 				Config: map[string]interface{}{ |  | ||||||
| 					"authentik_host": ak.TestSecret(), |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			ak.MockConfig(), |  | ||||||
| 		), |  | ||||||
| 	) | 	) | ||||||
|  | 	ts.apps = append(ts.apps, a) | ||||||
| 	return a | 	return a | ||||||
| } | } | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ func urlJoin(originalUrl string, newPath string) string { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { | func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	s, err := a.sessions.Get(r, constants.SessionName) | 	s, err := a.sessions.Get(r, a.SessionName()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		a.log.WithError(err).Warning("failed to decode session") | 		a.log.WithError(err).Warning("failed to decode session") | ||||||
| 	} | 	} | ||||||
| @ -74,7 +74,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { | |||||||
|  |  | ||||||
| func (a *Application) redirect(rw http.ResponseWriter, r *http.Request) { | func (a *Application) redirect(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	redirect := a.proxyConfig.ExternalHost | 	redirect := a.proxyConfig.ExternalHost | ||||||
| 	s, _ := a.sessions.Get(r, constants.SessionName) | 	s, _ := a.sessions.Get(r, a.SessionName()) | ||||||
| 	redirectR, ok := s.Values[constants.SessionRedirect] | 	redirectR, ok := s.Values[constants.SessionRedirect] | ||||||
| 	if ok { | 	if ok { | ||||||
| 		redirect = redirectR.(string) | 		redirect = redirectR.(string) | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ func TestRedirectToStart_Proxy(t *testing.T) { | |||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io%2Ffoo%2Fbar%2Fbaz", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io%2Ffoo%2Fbar%2Fbaz", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -40,7 +40,7 @@ func TestRedirectToStart_Forward(t *testing.T) { | |||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io%2Ffoo%2Fbar%2Fbaz", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io%2Ffoo%2Fbar%2Fbaz", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -58,7 +58,7 @@ func TestRedirectToStart_Forward_Domain_Invalid(t *testing.T) { | |||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -76,6 +76,6 @@ func TestRedirectToStart_Forward_Domain(t *testing.T) { | |||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start?rd=https%3A%2F%2Ftest.goauthentik.io", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SessionName) | 	s, _ := a.sessions.Get(req, a.SessionName()) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								internal/outpost/proxyv2/codecs/codec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								internal/outpost/proxyv2/codecs/codec.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | package codecs | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/gorilla/securecookie" | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Codec struct { | ||||||
|  | 	*securecookie.SecureCookie | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(maxAge int, hashKey, blockKey []byte) *Codec { | ||||||
|  | 	cookie := securecookie.New(hashKey, blockKey) | ||||||
|  | 	cookie.MaxAge(maxAge) | ||||||
|  | 	return &Codec{ | ||||||
|  | 		SecureCookie: cookie, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func CodecsFromPairs(maxAge int, keyPairs ...[]byte) []securecookie.Codec { | ||||||
|  | 	codecs := make([]securecookie.Codec, len(keyPairs)/2+len(keyPairs)%2) | ||||||
|  | 	for i := 0; i < len(keyPairs); i += 2 { | ||||||
|  | 		var blockKey []byte | ||||||
|  | 		if i+1 < len(keyPairs) { | ||||||
|  | 			blockKey = keyPairs[i+1] | ||||||
|  | 		} | ||||||
|  | 		codecs[i/2] = New(maxAge, keyPairs[i], blockKey) | ||||||
|  | 	} | ||||||
|  | 	return codecs | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Codec) Encode(name string, value interface{}) (string, error) { | ||||||
|  | 	log.Trace("cookie encode") | ||||||
|  | 	return s.SecureCookie.Encode("authentik_proxy", value) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Codec) Decode(name string, value string, dst interface{}) error { | ||||||
|  | 	log.Trace("cookie decode") | ||||||
|  | 	return s.SecureCookie.Decode("authentik_proxy", value, dst) | ||||||
|  | } | ||||||
| @ -1,7 +1,5 @@ | |||||||
| package constants | package constants | ||||||
|  |  | ||||||
| const SessionName = "authentik_proxy" |  | ||||||
|  |  | ||||||
| const SessionOAuthState = "oauth_state" | const SessionOAuthState = "oauth_state" | ||||||
| const SessionClaims = "claims" | const SessionClaims = "claims" | ||||||
|  |  | ||||||
|  | |||||||
| @ -50,7 +50,9 @@ func NewProxyServer(ac *ak.APIController) *ProxyServer { | |||||||
|  |  | ||||||
| 	globalMux := rootMux.NewRoute().Subrouter() | 	globalMux := rootMux.NewRoute().Subrouter() | ||||||
| 	globalMux.Use(web.NewLoggingHandler(l.WithField("logger", "authentik.outpost.proxyv2.http"), nil)) | 	globalMux.Use(web.NewLoggingHandler(l.WithField("logger", "authentik.outpost.proxyv2.http"), nil)) | ||||||
|  | 	if ac.GlobalConfig.ErrorReporting.Enabled { | ||||||
| 		globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) | 		globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) | ||||||
|  | 	} | ||||||
| 	s := &ProxyServer{ | 	s := &ProxyServer{ | ||||||
| 		cryptoStore: ak.NewCryptoStore(ac.Client.CryptoApi), | 		cryptoStore: ak.NewCryptoStore(ac.Client.CryptoApi), | ||||||
| 		apps:        make(map[string]*application.Application), | 		apps:        make(map[string]*application.Application), | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import ( | |||||||
| 	"goauthentik.io/internal/outpost/ak" | 	"goauthentik.io/internal/outpost/ak" | ||||||
| 	"goauthentik.io/internal/outpost/proxyv2/application" | 	"goauthentik.io/internal/outpost/proxyv2/application" | ||||||
| 	"goauthentik.io/internal/utils/web" | 	"goauthentik.io/internal/utils/web" | ||||||
|  | 	"golang.org/x/exp/maps" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (ps *ProxyServer) Refresh() error { | func (ps *ProxyServer) Refresh() error { | ||||||
| @ -33,7 +34,7 @@ func (ps *ProxyServer) Refresh() error { | |||||||
| 				), | 				), | ||||||
| 			), | 			), | ||||||
| 		} | 		} | ||||||
| 		a, err := application.NewApplication(provider, hc, ps.cryptoStore, ps.akAPI) | 		a, err := application.NewApplication(provider, hc, ps) | ||||||
| 		existing, ok := apps[a.Host] | 		existing, ok := apps[a.Host] | ||||||
| 		if ok { | 		if ok { | ||||||
| 			existing.Stop() | 			existing.Stop() | ||||||
| @ -48,3 +49,15 @@ func (ps *ProxyServer) Refresh() error { | |||||||
| 	ps.log.Debug("Swapped maps") | 	ps.log.Debug("Swapped maps") | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (ps *ProxyServer) API() *ak.APIController { | ||||||
|  | 	return ps.akAPI | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ps *ProxyServer) CryptoStore() *ak.CryptoStore { | ||||||
|  | 	return ps.cryptoStore | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ps *ProxyServer) Apps() []*application.Application { | ||||||
|  | 	return maps.Values(ps.apps) | ||||||
|  | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L