mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
outposts: update permissions more eagerly (#17783)
* wip * wip * a * a Signed-off-by: Dominic R <dominic@sdko.org> * rm * this * rm test files * cover one more case Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Dominic R <dominic@sdko.org> Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user