diff --git a/authentik/outposts/signals.py b/authentik/outposts/signals.py index aa8e9e472d..4adba5ea7c 100644 --- a/authentik/outposts/signals.py +++ b/authentik/outposts/signals.py @@ -49,6 +49,9 @@ def outpost_m2m_changed(sender, instance: Outpost | Provider, action: str, **_): if action not in ["post_add", "post_remove", "post_clear"]: return if isinstance(instance, Outpost): + # Rebuild permissions when providers change + LOGGER.debug("Rebuilding outpost service account permissions", outpost=instance) + instance.build_user_permissions(instance.user) outpost_controller.send_with_options( args=(instance.pk,), rel_obj=instance.service_connection, @@ -74,10 +77,9 @@ def outpost_m2m_changed(sender, instance: Outpost | Provider, action: str, **_): @receiver(post_save, sender=Outpost) -def outpost_post_save(sender, instance: Outpost, created: bool, **_): - if created: - LOGGER.info("New outpost saved, ensuring initial token and user are created") - _ = instance.token +def outpost_post_save(sender, instance: Outpost, **_): + LOGGER.info("Outpost saved, ensuring token and user are created and permissions are set") + _ = instance.token outpost_controller.send_with_options( args=(instance.pk,), rel_obj=instance.service_connection, @@ -92,6 +94,15 @@ def outpost_post_save(sender, instance: Outpost, created: bool, **_): def outpost_related_post_save(sender, instance: OutpostServiceConnection | OutpostModel, **_): for outpost in instance.outpost_set.all(): + # Rebuild permissions in case provider's required objects changed + if isinstance(instance, OutpostModel): + LOGGER.info( + "Provider changed, rebuilding permissions and sending update", + outpost=outpost.name, + provider=instance.name if hasattr(instance, "name") else str(instance), + ) + outpost.build_user_permissions(outpost.user) + LOGGER.debug("Sending update to outpost", outpost=outpost.name, trigger="provider_change") outpost_send_update.send_with_options( args=(outpost.pk,), rel_obj=outpost, diff --git a/internal/outpost/ak/api.go b/internal/outpost/ak/api.go index cc755c3cc9..76acbf7429 100644 --- a/internal/outpost/ak/api.go +++ b/internal/outpost/ak/api.go @@ -93,7 +93,7 @@ func NewAPIController(akURL url.URL, token string) *APIController { }), ) if len(outposts.Results) < 1 { - log.Panic("No outposts found with given token, ensure the given token corresponds to an authenitk Outpost") + log.Panic("No outposts found with given token, ensure the given token corresponds to an authentik Outpost") } outpost := outposts.Results[0] @@ -122,6 +122,7 @@ func NewAPIController(akURL url.URL, token string) *APIController { eventHandlers: []EventHandler{}, refreshHandlers: make([]func(), 0), } + ac.logger.WithField("embedded", ac.IsEmbedded()).Info("Outpost mode") ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset") err = ac.initEvent(akURL, outpost.Pk) if err != nil { @@ -135,6 +136,13 @@ func (a *APIController) Log() *log.Entry { return a.logger } +func (a *APIController) IsEmbedded() bool { + if m := a.Outpost.Managed.Get(); m != nil { + return *m == "goauthentik.io/outposts/embedded" + } + return false +} + // Start Starts all handlers, non-blocking func (a *APIController) Start() error { err := a.Server.Refresh() diff --git a/internal/outpost/proxyv2/application/application.go b/internal/outpost/proxyv2/application/application.go index c9eac19ad2..3a428d46b9 100644 --- a/internal/outpost/proxyv2/application/application.go +++ b/internal/outpost/proxyv2/application/application.go @@ -66,6 +66,7 @@ type Server interface { API() *ak.APIController Apps() []*Application CryptoStore() *ak.CryptoStore + SessionBackend() string } func init() { @@ -94,10 +95,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, old CallbackSignature: []string{"true"}, }.Encode() - isEmbedded := false - if m := server.API().Outpost.Managed.Get(); m != nil { - isEmbedded = *m == "goauthentik.io/outposts/embedded" - } + isEmbedded := server.API().IsEmbedded() // Configure an OpenID Connect aware OAuth2 client. endpoint := GetOIDCEndpoint( p, @@ -153,6 +151,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, old go a.authHeaderCache.Start() if oldApp != nil && oldApp.sessions != nil { a.sessions = oldApp.sessions + muxLogger.Debug("reusing existing session store") } else { sess, err := a.getStore(p, externalHost) if err != nil { diff --git a/internal/outpost/proxyv2/application/session.go b/internal/outpost/proxyv2/application/session.go index db253e6e6e..361bf2b9aa 100644 --- a/internal/outpost/proxyv2/application/session.go +++ b/internal/outpost/proxyv2/application/session.go @@ -29,7 +29,10 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) // Add one to the validity to ensure we don't have a session with indefinite length maxAge = int(*t) + 1 } - if a.isEmbedded { + + sessionBackend := a.srv.SessionBackend() + switch sessionBackend { + case "postgres": // New PostgreSQL store ps, err := postgresstore.NewPostgresStore(a.log) if err != nil { @@ -46,30 +49,32 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) Path: "/", }) - a.log.Trace("using postgresql session backend") return ps, nil - } - dir := os.TempDir() - cs, err := filesystemstore.GetPersistentStore(dir) - if err != nil { - return nil, err - } - 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 + case "filesystem": + dir := os.TempDir() + cs, err := filesystemstore.GetPersistentStore(dir) + if err != nil { + return nil, err + } + 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.HttpOnly = true - cs.Options.Secure = strings.ToLower(externalHost.Scheme) == "https" - cs.Options.Domain = *p.CookieDomain - cs.Options.SameSite = http.SameSiteLaxMode - cs.Options.MaxAge = maxAge - cs.Options.Path = "/" - a.log.WithField("dir", dir).Trace("using filesystem session backend") - return cs, nil + // 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.HttpOnly = true + cs.Options.Secure = strings.ToLower(externalHost.Scheme) == "https" + cs.Options.Domain = *p.CookieDomain + cs.Options.SameSite = http.SameSiteLaxMode + cs.Options.MaxAge = maxAge + cs.Options.Path = "/" + return cs, nil + default: + a.log.WithField("backend", sessionBackend).Panic("unknown session backend type") + return nil, nil + } } func (a *Application) SessionName() string { diff --git a/internal/outpost/proxyv2/application/test.go b/internal/outpost/proxyv2/application/test.go index 468a6441d0..ecc095a404 100644 --- a/internal/outpost/proxyv2/application/test.go +++ b/internal/outpost/proxyv2/application/test.go @@ -41,6 +41,10 @@ func (ts *testServer) Apps() []*Application { return ts.apps } +func (ts *testServer) SessionBackend() string { + return "filesystem" +} + func newTestApplication() *Application { ts := newTestServer() a, _ := NewApplication( diff --git a/internal/outpost/proxyv2/proxyv2.go b/internal/outpost/proxyv2/proxyv2.go index 65c74469b9..ffc5d90d31 100644 --- a/internal/outpost/proxyv2/proxyv2.go +++ b/internal/outpost/proxyv2/proxyv2.go @@ -55,6 +55,11 @@ func NewProxyServer(ac *ak.APIController) ak.Outpost { if ac.GlobalConfig.ErrorReporting.Enabled { globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) } + if ac.IsEmbedded() { + l.Info("using PostgreSQL session backend") + } else { + l.Info("using filesystem session backend") + } s := &ProxyServer{ cryptoStore: ak.NewCryptoStore(ac.Client.CryptoApi), apps: make(map[string]*application.Application), diff --git a/internal/outpost/proxyv2/refresh.go b/internal/outpost/proxyv2/refresh.go index b4112b4316..3ce7435ab7 100644 --- a/internal/outpost/proxyv2/refresh.go +++ b/internal/outpost/proxyv2/refresh.go @@ -15,7 +15,9 @@ import ( ) func (ps *ProxyServer) Refresh() error { - providers, err := ak.Paginator(ps.akAPI.Client.OutpostsApi.OutpostsProxyList(context.Background()), ak.PaginatorOptions{ + req := ps.akAPI.Client.OutpostsApi.OutpostsProxyList(context.Background()) + ps.log.WithField("outpost_pk", ps.akAPI.Outpost.Pk).Debug("Requesting providers for outpost") + providers, err := ak.Paginator(req, ak.PaginatorOptions{ PageSize: 100, Logger: ps.log, }) @@ -25,6 +27,13 @@ func (ps *ProxyServer) Refresh() error { if err != nil { return err } + ps.log.WithField("count", len(providers)).Debug("Fetched providers") + if len(providers) == 0 { + ps.log.Warning("No providers assigned to this outpost, check outpost configuration in authentik") + } + for i, p := range providers { + ps.log.WithField("index", i).WithField("name", p.Name).WithField("external_host", p.ExternalHost).WithField("assigned_to_app", p.AssignedApplicationName).Debug("Provider details") + } apps := make(map[string]*application.Application) for _, provider := range providers { rsp := sentry.StartSpan(context.Background(), "authentik.outposts.proxy.application_ss") @@ -52,6 +61,7 @@ func (ps *ProxyServer) Refresh() error { ps.log.WithError(err).Warning("failed to setup application") continue } + ps.log.WithField("name", provider.Name).WithField("host", externalHost.Host).Info("Loaded application") apps[externalHost.Host] = a } ps.apps = apps @@ -70,3 +80,14 @@ func (ps *ProxyServer) CryptoStore() *ak.CryptoStore { func (ps *ProxyServer) Apps() []*application.Application { return maps.Values(ps.apps) } + +func (ps *ProxyServer) SessionBackend() string { + if ps.akAPI.IsEmbedded() { + return "postgres" + } + if !ps.akAPI.IsEmbedded() { + return "filesystem" + } + ps.log.Panic("failed to determine session backend type") + return "" +}