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/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
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-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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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 {
|
||||
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")
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
const SessionName = "authentik_proxy"
|
||||
|
||||
const SessionOAuthState = "oauth_state"
|
||||
const SessionClaims = "claims"
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Reference in New Issue