diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index d0cc78e7d..d669d66e9 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -60,7 +60,7 @@ class OutpostConfig: kubernetes_replicas: int = field(default=1) kubernetes_namespace: str = field(default="default") kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict) - kubernetes_ingress_secret_name: str = field(default="authentik-outpost") + kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls") kubernetes_service_type: str = field(default="ClusterIP") diff --git a/authentik/outposts/signals.py b/authentik/outposts/signals.py index 391b85c7e..edf85dc0a 100644 --- a/authentik/outposts/signals.py +++ b/authentik/outposts/signals.py @@ -1,15 +1,16 @@ """authentik outpost signals""" from django.conf import settings from django.db.models import Model -from django.db.models.signals import post_save, pre_delete +from django.db.models.signals import post_save, pre_delete, pre_save from django.dispatch import receiver from structlog.stdlib import get_logger from authentik.core.models import Provider from authentik.crypto.models import CertificateKeyPair from authentik.lib.utils.reflection import class_to_path +from authentik.outposts.controllers.base import ControllerException from authentik.outposts.models import Outpost, OutpostServiceConnection -from authentik.outposts.tasks import outpost_post_save, outpost_pre_delete +from authentik.outposts.tasks import outpost_controller_down, outpost_post_save LOGGER = get_logger() UPDATE_TRIGGERING_MODELS = ( @@ -20,6 +21,27 @@ UPDATE_TRIGGERING_MODELS = ( ) +@receiver(pre_save, sender=Outpost) +# pylint: disable=unused-argument +def pre_save_outpost(sender, instance: Outpost, **_): + """Pre-save checks for an outpost, if the name or config.kubernetes_namespace changes, + we call down and then wait for the up after save""" + old_instances = Outpost.objects.filter(pk=instance.pk) + if not old_instances.exists(): + return + old_instance = old_instances.first() + dirty = False + # Name changes the deployment name, need to recreate + dirty += old_instance.name != instance.name + # namespace requires re-create + dirty += ( + old_instance.config.kubernetes_namespace != instance.config.kubernetes_namespace + ) + if bool(dirty): + LOGGER.info("Outpost needs re-deployment due to changes", instance=instance) + outpost_controller_down_wrapper(old_instance) + + @receiver(post_save) # pylint: disable=unused-argument def post_save_update(sender, instance: Model, **_): @@ -41,11 +63,15 @@ def post_save_update(sender, instance: Model, **_): def pre_delete_cleanup(sender, instance: Outpost, **_): """Ensure that Outpost's user is deleted (which will delete the token through cascade)""" instance.user.delete() - # To ensure that deployment is cleaned up *consistently* we call the controller, and wait - # for it to finish. We don't want to call it in this thread, as we don't have the Outpost - # Service connection here + outpost_controller_down_wrapper(instance) + + +def outpost_controller_down_wrapper(instance: Outpost): + """To ensure that deployment is cleaned up *consistently* we call the controller, and wait + for it to finish. We don't want to call it in this thread, as we don't have the Outpost + Service connection here""" try: - outpost_pre_delete.delay(instance.pk.hex).get() + outpost_controller_down.delay(instance.pk.hex).get() except RuntimeError: # pragma: no cover # In e2e/integration tests, this might run inside a thread/process and # trigger the celery `Never call result.get() within a task` detection @@ -53,3 +79,7 @@ def pre_delete_cleanup(sender, instance: Outpost, **_): pass else: raise + except ControllerException as exc: + LOGGER.warning( + "failed to cleanup outpost deployment", exc=exc, instance=instance + ) diff --git a/authentik/outposts/tasks.py b/authentik/outposts/tasks.py index aa5d569ce..2c247fc83 100644 --- a/authentik/outposts/tasks.py +++ b/authentik/outposts/tasks.py @@ -111,7 +111,7 @@ def outpost_controller(self: MonitoredTask, outpost_pk: str): @CELERY_APP.task() -def outpost_pre_delete(outpost_pk: str): +def outpost_controller_down(outpost_pk: str): """Delete outpost objects before deleting the DB Object""" outpost = Outpost.objects.get(pk=outpost_pk) controller = controller_for_outpost(outpost)