providers/proxy: different cookie name based on hashed client id (#4666)

This commit is contained in:
Jens L 2023-02-12 16:34:57 +01:00 committed by GitHub
parent e490d25791
commit 21e29744c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 199 additions and 91 deletions

3
go.mod
View File

@ -26,6 +26,7 @@ require (
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
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/sync v0.0.0-20220601150217-0de741cfad7f
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b
@ -71,7 +72,7 @@ require (
go.opentelemetry.io/otel/trace v1.11.1 // indirect
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // 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
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect

5
go.sum
View File

@ -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-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-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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -2,6 +2,7 @@ package application
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/gob"
"fmt"
@ -40,6 +41,7 @@ type Application struct {
oauthConfig oauth2.Config
tokenVerifier *oidc.IDTokenVerifier
outpostName string
sessionName string
sessions sessions.Store
proxyConfig api.ProxyOutpostConfig
@ -48,12 +50,19 @@ type Application struct {
log *log.Entry
mux *mux.Router
ak *ak.APIController
srv Server
errorTemplates *template.Template
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{})
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()
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"
}
// Configure an OpenID Connect aware OAuth2 client.
endpoint := GetOIDCEndpoint(
p,
ak.Outpost.Config["authentik_host"].(string),
server.API().Outpost.Config["authentik_host"].(string),
managed,
)
@ -100,10 +109,16 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
Scopes: p.ScopesToRequest,
}
mux := mux.NewRouter()
h := sha256.New()
bs := string(h.Sum([]byte(*p.ClientId)))
sessionName := fmt.Sprintf("authentik_proxy_%s", bs[:8])
a := &Application{
Host: externalHost.Host,
log: muxLogger,
outpostName: ak.Outpost.Name,
outpostName: server.API().Outpost.Name,
sessionName: sessionName,
endpoint: endpoint,
oauthConfig: oauth2Config,
tokenVerifier: verifier,
@ -111,8 +126,9 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
httpClient: c,
mux: mux,
errorTemplates: templates.GetTemplates(),
ak: ak,
ak: server.API(),
authHeaderCache: ttlcache.New(ttlcache.WithDisableTouchOnHit[string, Claims]()),
srv: server,
}
go a.authHeaderCache.Start()
a.sessions = a.getStore(p, externalHost)
@ -153,7 +169,9 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
}).Observe(float64(after))
})
})
if server.API().GlobalConfig.ErrorReporting.Enabled {
mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
}
mux.Use(func(inner http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
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 {
err := cs.AddKeypair(*kp)
err := server.CryptoStore().AddKeypair(*kp)
if err != nil {
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 != "" {
@ -235,7 +253,7 @@ func (a *Application) Stop() {
func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) {
redirect := a.endpoint.EndSessionEndpoint
s, err := a.sessions.Get(r, constants.SessionName)
s, err := a.sessions.Get(r, a.SessionName())
if err != nil {
a.redirectToStart(rw, r)
return

View File

@ -49,7 +49,7 @@ func (a *Application) checkAuth(rw http.ResponseWriter, r *http.Request) (*Claim
}
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 {
// err == user has no session/session is not valid, reject
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) {
s, _ := a.sessions.Get(r, constants.SessionName)
s, _ := a.sessions.Get(r, a.SessionName())
s.Values[constants.SessionClaims] = claims
err := s.Save(r, rw)

View File

@ -20,7 +20,7 @@ func (a *Application) ErrorPage(rw http.ResponseWriter, r *http.Request, err str
Message: "Error proxying to upstream server",
ProxyPrefix: "/outpost.goauthentik.io",
}
if claims != nil && claims.Proxy.IsSuperuser {
if claims != nil && claims.Proxy != nil && claims.Proxy.IsSuperuser {
data.Message = err
} else {
data.Message = "Failed to connect to backend."

View File

@ -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 the application
// 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 {
s.Values[constants.SessionRedirect] = fwd.String()
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 the application
// 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 {
s.Values[constants.SessionRedirect] = fwd.String()
err = s.Save(r, rw)
@ -151,7 +151,7 @@ func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request
return
}
s, _ := a.sessions.Get(r, constants.SessionName)
s, _ := a.sessions.Get(r, a.SessionName())
if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
s.Values[constants.SessionRedirect] = fwd.String()
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 the application
// 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 {
s.Values[constants.SessionRedirect] = fwd.String()
err = s.Save(r, rw)

View File

@ -48,7 +48,7 @@ func TestForwardHandleCaddy_Single_Headers(t *testing.T) {
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
shouldUrl := url.Values{
"client_id": []string{*a.proxyConfig.ClientId},
"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()
a.forwardHandleCaddy(rr, req)
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = Claims{
@ -135,7 +135,7 @@ func TestForwardHandleCaddy_Domain_Header(t *testing.T) {
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
shouldUrl := url.Values{
"client_id": []string{*a.proxyConfig.ClientId},
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},

View File

@ -33,7 +33,7 @@ func TestForwardHandleEnvoy_Single_Headers(t *testing.T) {
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
shouldUrl := url.Values{
"client_id": []string{*a.proxyConfig.ClientId},
"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()
a.forwardHandleEnvoy(rr, req)
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = Claims{
@ -103,7 +103,7 @@ func TestForwardHandleEnvoy_Domain_Header(t *testing.T) {
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
shouldUrl := url.Values{
"client_id": []string{*a.proxyConfig.ClientId},

View File

@ -42,7 +42,7 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) {
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])
}
@ -56,7 +56,7 @@ func TestForwardHandleNginx_Single_URI(t *testing.T) {
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])
}
@ -68,7 +68,7 @@ func TestForwardHandleNginx_Single_Claims(t *testing.T) {
rr := httptest.NewRecorder()
a.forwardHandleNginx(rr, req)
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = Claims{
@ -132,6 +132,6 @@ func TestForwardHandleNginx_Domain_Header(t *testing.T) {
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])
}

View File

@ -48,7 +48,7 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
shouldUrl := url.Values{
"client_id": []string{*a.proxyConfig.ClientId},
"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()
a.forwardHandleTraefik(rr, req)
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = Claims{
@ -135,7 +135,7 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) {
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
shouldUrl := url.Values{
"client_id": []string{*a.proxyConfig.ClientId},
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},

View File

@ -70,7 +70,7 @@ func TestProxy_ModifyRequest_Claims(t *testing.T) {
}
rr := httptest.NewRecorder()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = Claims{
@ -100,7 +100,7 @@ func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) {
}
rr := httptest.NewRecorder()
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = Claims{

View File

@ -45,7 +45,7 @@ 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, constants.SessionName)
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
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) {
s, err := a.sessions.Get(r, constants.SessionName)
s, err := a.sessions.Get(r, a.SessionName())
if err != nil {
a.log.WithError(err).Trace("failed to get session")
}

View File

@ -14,6 +14,7 @@ import (
"github.com/gorilla/sessions"
"goauthentik.io/api/v3"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/proxyv2/codecs"
"goauthentik.io/internal/outpost/proxyv2/constants"
"gopkg.in/boj/redistore.v1"
)
@ -21,27 +22,28 @@ import (
const RedisKeyPrefix = "authentik_proxy_session_"
func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) sessions.Store {
var store sessions.Store
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)
maxAge := 0
if p.AccessTokenValidity.IsSet() {
t := p.AccessTokenValidity.Get()
// Add one to the validity to ensure we don't have a session with indefinite length
rs.SetMaxAge(int(*t) + 1)
} else {
rs.SetMaxAge(0)
maxAge = int(*t) + 1
}
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
a.log.Trace("using redis session backend")
store = rs
} else {
return rs
}
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
// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with:
// 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
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
a.log.WithField("dir", dir).Trace("using filesystem session backend")
store = cs
return cs
}
return store
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 cs
}
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())
if err != nil {
return err
@ -81,8 +87,8 @@ func (a *Application) Logout(sub string) error {
continue
}
err = securecookie.DecodeMulti(
constants.SessionName, string(data),
&s.Values, fs.Codecs...,
a.SessionName(), string(data),
&s.Values, a.getAllCodecs()...,
)
if err != nil {
a.log.WithError(err).Trace("failed to decode session")

View File

@ -20,7 +20,7 @@ func TestLogout(t *testing.T) {
rr := httptest.NewRecorder()
// Login once
s, _ := a.sessions.Get(req, constants.SessionName)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = Claims{
@ -36,7 +36,7 @@ func TestLogout(t *testing.T) {
assert.Equal(t, http.StatusBadGateway, rr.Code)
// Login twice
s2, _ := a.sessions.Get(req, constants.SessionName)
s2, _ := a.sessions.Get(req, a.SessionName())
s2.ID = uuid.New().String()
s2.Options.MaxAge = 86400
s2.Values[constants.SessionClaims] = Claims{
@ -53,7 +53,7 @@ func TestLogout(t *testing.T) {
// Logout
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.Options.MaxAge = 86400
s3.Values[constants.SessionClaims] = Claims{

View File

@ -7,7 +7,39 @@ import (
"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 {
ts := newTestServer()
a, _ := NewApplication(
api.ProxyOutpostConfig{
Name: ak.TestSecret(),
@ -30,15 +62,8 @@ func newTestApplication() *Application {
},
},
http.DefaultClient,
nil,
ak.MockAK(
api.Outpost{
Config: map[string]interface{}{
"authentik_host": ak.TestSecret(),
},
},
ak.MockConfig(),
),
ts,
)
ts.apps = append(ts.apps, a)
return a
}

View File

@ -30,7 +30,7 @@ func urlJoin(originalUrl string, newPath string) string {
}
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 {
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) {
redirect := a.proxyConfig.ExternalHost
s, _ := a.sessions.Get(r, constants.SessionName)
s, _ := a.sessions.Get(r, a.SessionName())
redirectR, ok := s.Values[constants.SessionRedirect]
if ok {
redirect = redirectR.(string)

View File

@ -23,7 +23,7 @@ func TestRedirectToStart_Proxy(t *testing.T) {
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())
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])
}
@ -40,7 +40,7 @@ func TestRedirectToStart_Forward(t *testing.T) {
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())
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])
}
@ -58,7 +58,7 @@ func TestRedirectToStart_Forward_Domain_Invalid(t *testing.T) {
loc, _ := rr.Result().Location()
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])
}
@ -76,6 +76,6 @@ func TestRedirectToStart_Forward_Domain(t *testing.T) {
loc, _ := rr.Result().Location()
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])
}

View 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)
}

View File

@ -1,7 +1,5 @@
package constants
const SessionName = "authentik_proxy"
const SessionOAuthState = "oauth_state"
const SessionClaims = "claims"

View File

@ -50,7 +50,9 @@ func NewProxyServer(ac *ak.APIController) *ProxyServer {
globalMux := rootMux.NewRoute().Subrouter()
globalMux.Use(web.NewLoggingHandler(l.WithField("logger", "authentik.outpost.proxyv2.http"), nil))
if ac.GlobalConfig.ErrorReporting.Enabled {
globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
}
s := &ProxyServer{
cryptoStore: ak.NewCryptoStore(ac.Client.CryptoApi),
apps: make(map[string]*application.Application),

View File

@ -10,6 +10,7 @@ import (
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2/application"
"goauthentik.io/internal/utils/web"
"golang.org/x/exp/maps"
)
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]
if ok {
existing.Stop()
@ -48,3 +49,15 @@ func (ps *ProxyServer) Refresh() error {
ps.log.Debug("Swapped maps")
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)
}