simplify outpost signals

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2025-06-26 15:00:26 +02:00
parent 8f4353181e
commit f264989f9e
4 changed files with 50 additions and 76 deletions

View File

@ -173,6 +173,7 @@ class OutpostServiceConnection(ScheduledModel, models.Model):
uid=self.pk,
args=(self.pk,),
crontab="3-59/15 * * * *",
send_on_save=True,
),
]
@ -325,6 +326,7 @@ class Outpost(ScheduledModel, SerializerModel, ManagedModel):
args=(self.pk,),
kwargs={"action": "up", "from_cache": False},
crontab=f"{fqdn_rand('outpost_controller')} */4 * * *",
send_on_save=True,
),
]

View File

@ -10,12 +10,12 @@ from authentik.brands.models import Brand
from authentik.core.models import AuthenticatedSession, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.utils.reflection import class_to_path
from authentik.outposts.models import Outpost, OutpostServiceConnection
from authentik.outposts.models import Outpost, OutpostModel, OutpostServiceConnection
from authentik.outposts.tasks import (
CACHE_KEY_OUTPOST_DOWN,
outpost_controller,
outpost_send_update,
outpost_session_end,
outposts_and_related_update_dispatch,
)
LOGGER = get_logger()
@ -45,28 +45,58 @@ def outpost_pre_save(sender, instance: Outpost, **_):
@receiver(m2m_changed, sender=Outpost.providers.through)
def outpost_m2m_changed(sender, instance: Model, action: str, **_):
def outpost_m2m_changed(sender, instance: Provider, action: str, **_):
"""Update outpost on m2m change, when providers are added or removed"""
if action in ["post_add", "post_remove", "post_clear"]:
outposts_and_related_update_dispatch.send(class_to_path(instance.__class__), instance.pk)
if not isinstance(instance, OutpostModel):
return
for outpost in instance.outpost_set.all():
outpost_send_update.send_with_options(args=(outpost.pk,), rel_obj=outpost)
def outposts_and_related_post_save(sender, instance: Model, created: bool, **_):
"""If an Outpost is saved, Ensure that token is created/updated
If an OutpostModel, or a model that is somehow connected to an OutpostModel is saved,
we send a message down the relevant OutpostModels WS connection to trigger an update"""
if isinstance(instance, Outpost) and created:
@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
outposts_and_related_update_dispatch.send(class_to_path(instance.__class__), instance.pk)
outpost_send_update.send_with_options(args=(instance.pk,), rel_obj=instance)
post_save.connect(outposts_and_related_post_save, sender=Outpost, weak=False)
post_save.connect(outposts_and_related_post_save, sender=OutpostServiceConnection, weak=False)
post_save.connect(outposts_and_related_post_save, sender=Provider, weak=False)
post_save.connect(outposts_and_related_post_save, sender=CertificateKeyPair, weak=False)
post_save.connect(outposts_and_related_post_save, sender=Brand, weak=False)
def outpost_related_post_save(sender, instance: OutpostServiceConnection | OutpostModel, **_):
for outpost in instance.outpost_set.all():
outpost_send_update.send_with_options(args=(outpost.pk,), rel_obj=outpost)
post_save.connect(outpost_related_post_save, sender=OutpostServiceConnection, weak=False)
post_save.connect(outpost_related_post_save, sender=OutpostModel, weak=False)
def outpost_reverse_related_post_save(sender, instance: CertificateKeyPair | Brand, **_):
for field in instance._meta.get_fields():
# Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms)
# are used, and if it has a value
if not hasattr(field, "related_model"):
continue
if not field.related_model:
continue
if not issubclass(field.related_model, OutpostModel):
continue
field_name = f"{field.name}_set"
if not hasattr(instance, field_name):
continue
LOGGER.debug("triggering outpost update from field", field=field.name)
# Because the Outpost Model has an M2M to Provider,
# we have to iterate over the entire QS
for reverse in getattr(instance, field_name).all():
if isinstance(reverse, OutpostModel):
for outpost in reverse.outpost_set.all():
outpost_send_update.send_with_options(args=(outpost.pk,), rel_obj=outpost)
post_save.connect(outpost_reverse_related_post_save, sender=Brand, weak=False)
post_save.connect(outpost_reverse_related_post_save, sender=CertificateKeyPair, weak=False)
@receiver(pre_delete, sender=Outpost)

View File

@ -153,65 +153,6 @@ def outpost_token_ensurer():
self.info(f"Successfully checked {len(all_outposts)} Outposts.")
@actor(description=_("Dispatch tasks to update outposts when related objects are updated."))
def outposts_and_related_update_dispatch(model_class: str, pk: Any):
"""If an Outpost is saved, Ensure that token is created/updated
If an OutpostModel, or a model that is somehow connected to an OutpostModel is saved,
we send a message down the relevant OutpostModels WS connection to trigger an update"""
model: Model = path_to_class(model_class)
try:
instance = model.objects.get(pk=pk)
except model.DoesNotExist:
LOGGER.warning("Model does not exist", model=model, pk=pk)
return
if isinstance(instance, Outpost):
LOGGER.debug("Trigger reconcile for outpost", instance=instance)
for schedule in instance.schedules.all():
schedule.send()
if isinstance(instance, OutpostModel | Outpost):
LOGGER.debug("triggering outpost update from outpostmodel/outpost", instance=instance)
outposts_and_related_send_update(instance)
if isinstance(instance, OutpostServiceConnection):
LOGGER.debug("triggering ServiceConnection state update", instance=instance)
for schedule in instance.schedules.all():
schedule.send()
for field in instance._meta.get_fields():
# Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms)
# are used, and if it has a value
if not hasattr(field, "related_model"):
continue
if not field.related_model:
continue
if not issubclass(field.related_model, OutpostModel):
continue
field_name = f"{field.name}_set"
if not hasattr(instance, field_name):
continue
LOGGER.debug("triggering outpost update from field", field=field.name)
# Because the Outpost Model has an M2M to Provider,
# we have to iterate over the entire QS
for reverse in getattr(instance, field_name).all():
outposts_and_related_send_update(reverse)
def outposts_and_related_send_update(model_instance: Model):
"""Send outpost update to all related outposts"""
if isinstance(model_instance, OutpostModel):
for outpost in model_instance.outpost_set.all():
outpost_send_update.send_with_options(args=(outpost.pk,), rel_obj=outpost)
elif isinstance(model_instance, Outpost):
outpost = model_instance
outpost_send_update.send_with_options(args=(outpost.pk,), rel_obj=outpost)
@actor(description=_("Send update to outpost"))
def outpost_send_update(pk: Any):
"""Update outpost instance"""

View File

@ -18,6 +18,7 @@ from authentik.events.models import Event, EventAction
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
from authentik.policies.models import PolicyBindingModel
from authentik.outposts.models import OutpostModel
LOGGER = get_logger()
@ -37,7 +38,7 @@ class AuthenticationMode(models.TextChoices):
PROMPT = "prompt"
class RACProvider(Provider):
class RACProvider(OutpostModel, Provider):
"""Remotely access computers/servers via RDP/SSH/VNC."""
settings = models.JSONField(default=dict)