providers/proxy: different cookie name based on hashed client id (#4666)
This commit is contained in:
parent
e490d25791
commit
21e29744c2
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))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
if server.API().GlobalConfig.ErrorReporting.Enabled {
|
||||||
|
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,50 +22,55 @@ 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 != "" {
|
if p.AccessTokenValidity.IsSet() {
|
||||||
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))
|
t := p.AccessTokenValidity.Get()
|
||||||
|
// Add one to the validity to ensure we don't have a session with indefinite length
|
||||||
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
rs.Codecs = codecs.CodecsFromPairs(maxAge, []byte(*p.CookieSecret))
|
||||||
rs.SetMaxLength(math.MaxInt)
|
rs.SetMaxLength(math.MaxInt)
|
||||||
rs.SetKeyPrefix(RedisKeyPrefix)
|
rs.SetKeyPrefix(RedisKeyPrefix)
|
||||||
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)
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
cs := sessions.NewFilesystemStore(dir, []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
|
|
||||||
// when using OpenID Connect , since this can contain a large amount of extra information in the id_token
|
|
||||||
|
|
||||||
// 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 store
|
dir := os.TempDir()
|
||||||
|
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
|
||||||
|
// when using OpenID Connect , since this can contain a large amount of extra information in the id_token
|
||||||
|
|
||||||
|
// 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.Options.Domain = *p.CookieDomain
|
||||||
|
a.log.WithField("dir", dir).Trace("using filesystem session backend")
|
||||||
|
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 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])
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
if ac.GlobalConfig.ErrorReporting.Enabled {
|
||||||
|
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