outposts: move local connection check to task, run every 60 minutes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		@ -1,17 +1,8 @@
 | 
			
		||||
"""authentik outposts app config"""
 | 
			
		||||
from importlib import import_module
 | 
			
		||||
from os import R_OK, access
 | 
			
		||||
from os.path import expanduser
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from socket import gethostname
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
 | 
			
		||||
import yaml
 | 
			
		||||
from django.apps import AppConfig
 | 
			
		||||
from django.db import ProgrammingError
 | 
			
		||||
from docker.constants import DEFAULT_UNIX_SOCKET
 | 
			
		||||
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
 | 
			
		||||
from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION
 | 
			
		||||
from structlog.stdlib import get_logger
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
@ -27,49 +18,8 @@ class AuthentikOutpostConfig(AppConfig):
 | 
			
		||||
    def ready(self):
 | 
			
		||||
        import_module("authentik.outposts.signals")
 | 
			
		||||
        try:
 | 
			
		||||
            AuthentikOutpostConfig.init_local_connection()
 | 
			
		||||
            from authentik.outposts.tasks import outpost_local_connection
 | 
			
		||||
 | 
			
		||||
            outpost_local_connection.delay()
 | 
			
		||||
        except ProgrammingError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def init_local_connection():
 | 
			
		||||
        """Check if local kubernetes or docker connections should be created"""
 | 
			
		||||
        from authentik.outposts.models import (
 | 
			
		||||
            DockerServiceConnection,
 | 
			
		||||
            KubernetesServiceConnection,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Explicitly check against token filename, as thats
 | 
			
		||||
        # only present when the integration is enabled
 | 
			
		||||
        if Path(SERVICE_TOKEN_FILENAME).exists():
 | 
			
		||||
            LOGGER.debug("Detected in-cluster Kubernetes Config")
 | 
			
		||||
            if not KubernetesServiceConnection.objects.filter(local=True).exists():
 | 
			
		||||
                LOGGER.debug("Created Service Connection for in-cluster")
 | 
			
		||||
                KubernetesServiceConnection.objects.create(
 | 
			
		||||
                    name="Local Kubernetes Cluster", local=True, kubeconfig={}
 | 
			
		||||
                )
 | 
			
		||||
        # For development, check for the existence of a kubeconfig file
 | 
			
		||||
        kubeconfig_path = expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
 | 
			
		||||
        if Path(kubeconfig_path).exists():
 | 
			
		||||
            LOGGER.debug("Detected kubeconfig")
 | 
			
		||||
            kubeconfig_local_name = f"k8s-{gethostname()}"
 | 
			
		||||
            if not KubernetesServiceConnection.objects.filter(
 | 
			
		||||
                name=kubeconfig_local_name
 | 
			
		||||
            ).exists():
 | 
			
		||||
                LOGGER.debug("Creating kubeconfig Service Connection")
 | 
			
		||||
                with open(kubeconfig_path, "r") as _kubeconfig:
 | 
			
		||||
                    KubernetesServiceConnection.objects.create(
 | 
			
		||||
                        name=kubeconfig_local_name,
 | 
			
		||||
                        kubeconfig=yaml.safe_load(_kubeconfig),
 | 
			
		||||
                    )
 | 
			
		||||
        unix_socket_path = urlparse(DEFAULT_UNIX_SOCKET).path
 | 
			
		||||
        socket = Path(unix_socket_path)
 | 
			
		||||
        if socket.exists() and access(socket, R_OK):
 | 
			
		||||
            LOGGER.debug("Detected local docker socket")
 | 
			
		||||
            if len(DockerServiceConnection.objects.filter(local=True)) == 0:
 | 
			
		||||
                LOGGER.debug("Created Service Connection for docker")
 | 
			
		||||
                DockerServiceConnection.objects.create(
 | 
			
		||||
                    name="Local Docker connection",
 | 
			
		||||
                    local=True,
 | 
			
		||||
                    url=unix_socket_path,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
@ -17,4 +17,9 @@ CELERY_BEAT_SCHEDULE = {
 | 
			
		||||
        "schedule": crontab(minute="*/5"),
 | 
			
		||||
        "options": {"queue": "authentik_scheduled"},
 | 
			
		||||
    },
 | 
			
		||||
    "outpost_local_connection": {
 | 
			
		||||
        "task": "authentik.outposts.tasks.outpost_local_connection",
 | 
			
		||||
        "schedule": crontab(minute="*/60"),
 | 
			
		||||
        "options": {"queue": "authentik_scheduled"},
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,20 @@
 | 
			
		||||
"""outpost tasks"""
 | 
			
		||||
from os import R_OK, access
 | 
			
		||||
from os.path import expanduser
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from socket import gethostname
 | 
			
		||||
from typing import Any
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
 | 
			
		||||
import yaml
 | 
			
		||||
from asgiref.sync import async_to_sync
 | 
			
		||||
from channels.layers import get_channel_layer
 | 
			
		||||
from django.core.cache import cache
 | 
			
		||||
from django.db.models.base import Model
 | 
			
		||||
from django.utils.text import slugify
 | 
			
		||||
from docker.constants import DEFAULT_UNIX_SOCKET
 | 
			
		||||
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
 | 
			
		||||
from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION
 | 
			
		||||
from structlog.stdlib import get_logger
 | 
			
		||||
 | 
			
		||||
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
 | 
			
		||||
@ -185,3 +194,42 @@ def _outpost_single_update(outpost: Outpost, layer=None):
 | 
			
		||||
    for state in OutpostState.for_outpost(outpost):
 | 
			
		||||
        LOGGER.debug("sending update", channel=state.uid, outpost=outpost)
 | 
			
		||||
        async_to_sync(layer.send)(state.uid, {"type": "event.update"})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@CELERY_APP.task()
 | 
			
		||||
def outpost_local_connection():
 | 
			
		||||
    """Checks the local environment and create Service connections."""
 | 
			
		||||
    # Explicitly check against token filename, as thats
 | 
			
		||||
    # only present when the integration is enabled
 | 
			
		||||
    if Path(SERVICE_TOKEN_FILENAME).exists():
 | 
			
		||||
        LOGGER.debug("Detected in-cluster Kubernetes Config")
 | 
			
		||||
        if not KubernetesServiceConnection.objects.filter(local=True).exists():
 | 
			
		||||
            LOGGER.debug("Created Service Connection for in-cluster")
 | 
			
		||||
            KubernetesServiceConnection.objects.create(
 | 
			
		||||
                name="Local Kubernetes Cluster", local=True, kubeconfig={}
 | 
			
		||||
            )
 | 
			
		||||
    # For development, check for the existence of a kubeconfig file
 | 
			
		||||
    kubeconfig_path = expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
 | 
			
		||||
    if Path(kubeconfig_path).exists():
 | 
			
		||||
        LOGGER.debug("Detected kubeconfig")
 | 
			
		||||
        kubeconfig_local_name = f"k8s-{gethostname()}"
 | 
			
		||||
        if not KubernetesServiceConnection.objects.filter(
 | 
			
		||||
            name=kubeconfig_local_name
 | 
			
		||||
        ).exists():
 | 
			
		||||
            LOGGER.debug("Creating kubeconfig Service Connection")
 | 
			
		||||
            with open(kubeconfig_path, "r") as _kubeconfig:
 | 
			
		||||
                KubernetesServiceConnection.objects.create(
 | 
			
		||||
                    name=kubeconfig_local_name,
 | 
			
		||||
                    kubeconfig=yaml.safe_load(_kubeconfig),
 | 
			
		||||
                )
 | 
			
		||||
    unix_socket_path = urlparse(DEFAULT_UNIX_SOCKET).path
 | 
			
		||||
    socket = Path(unix_socket_path)
 | 
			
		||||
    if socket.exists() and access(socket, R_OK):
 | 
			
		||||
        LOGGER.debug("Detected local docker socket")
 | 
			
		||||
        if len(DockerServiceConnection.objects.filter(local=True)) == 0:
 | 
			
		||||
            LOGGER.debug("Created Service Connection for docker")
 | 
			
		||||
            DockerServiceConnection.objects.create(
 | 
			
		||||
                name="Local Docker connection",
 | 
			
		||||
                local=True,
 | 
			
		||||
                url=unix_socket_path,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
@ -13,13 +13,13 @@ from selenium.webdriver.common.by import By
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik.core.models import Application
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.outposts.apps import AuthentikOutpostConfig
 | 
			
		||||
from authentik.outposts.models import (
 | 
			
		||||
    DockerServiceConnection,
 | 
			
		||||
    Outpost,
 | 
			
		||||
    OutpostConfig,
 | 
			
		||||
    OutpostType,
 | 
			
		||||
)
 | 
			
		||||
from authentik.outposts.tasks import outpost_local_connection
 | 
			
		||||
from authentik.providers.proxy.models import ProxyProvider
 | 
			
		||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
 | 
			
		||||
 | 
			
		||||
@ -117,7 +117,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
 | 
			
		||||
    @object_manager
 | 
			
		||||
    def test_proxy_connectivity(self):
 | 
			
		||||
        """Test proxy connectivity over websocket"""
 | 
			
		||||
        AuthentikOutpostConfig.init_local_connection()
 | 
			
		||||
        outpost_local_connection()
 | 
			
		||||
        proxy: ProxyProvider = ProxyProvider.objects.create(
 | 
			
		||||
            name="proxy_provider",
 | 
			
		||||
            authorization_flow=Flow.objects.get(
 | 
			
		||||
 | 
			
		||||
@ -12,9 +12,9 @@ from docker.types.healthcheck import Healthcheck
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik.crypto.models import CertificateKeyPair
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.outposts.apps import AuthentikOutpostConfig
 | 
			
		||||
from authentik.outposts.controllers.docker import DockerController
 | 
			
		||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
 | 
			
		||||
from authentik.outposts.tasks import outpost_local_connection
 | 
			
		||||
from authentik.providers.proxy.models import ProxyProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,7 @@ class OutpostDockerTests(TestCase):
 | 
			
		||||
        self.ssl_folder = mkdtemp()
 | 
			
		||||
        self.container = self._start_container(self.ssl_folder)
 | 
			
		||||
        # Ensure that local connection have been created
 | 
			
		||||
        AuthentikOutpostConfig.init_local_connection()
 | 
			
		||||
        outpost_local_connection()
 | 
			
		||||
        self.provider: ProxyProvider = ProxyProvider.objects.create(
 | 
			
		||||
            name="test",
 | 
			
		||||
            internal_host="http://localhost",
 | 
			
		||||
 | 
			
		||||
@ -3,11 +3,11 @@ from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.lib.config import CONFIG
 | 
			
		||||
from authentik.outposts.apps import AuthentikOutpostConfig
 | 
			
		||||
from authentik.outposts.controllers.k8s.base import NeedsUpdate
 | 
			
		||||
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
 | 
			
		||||
from authentik.outposts.controllers.kubernetes import KubernetesController
 | 
			
		||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
 | 
			
		||||
from authentik.outposts.tasks import outpost_local_connection
 | 
			
		||||
from authentik.providers.proxy.models import ProxyProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ class OutpostKubernetesTests(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        # Ensure that local connection have been created
 | 
			
		||||
        AuthentikOutpostConfig.init_local_connection()
 | 
			
		||||
        outpost_local_connection()
 | 
			
		||||
        self.provider: ProxyProvider = ProxyProvider.objects.create(
 | 
			
		||||
            name="test",
 | 
			
		||||
            internal_host="http://localhost",
 | 
			
		||||
 | 
			
		||||
@ -12,8 +12,8 @@ from docker.types.healthcheck import Healthcheck
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik.crypto.models import CertificateKeyPair
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.outposts.apps import AuthentikOutpostConfig
 | 
			
		||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
 | 
			
		||||
from authentik.outposts.tasks import outpost_local_connection
 | 
			
		||||
from authentik.providers.proxy.controllers.docker import DockerController
 | 
			
		||||
from authentik.providers.proxy.models import ProxyProvider
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,7 @@ class TestProxyDocker(TestCase):
 | 
			
		||||
        self.ssl_folder = mkdtemp()
 | 
			
		||||
        self.container = self._start_container(self.ssl_folder)
 | 
			
		||||
        # Ensure that local connection have been created
 | 
			
		||||
        AuthentikOutpostConfig.init_local_connection()
 | 
			
		||||
        outpost_local_connection()
 | 
			
		||||
        self.provider: ProxyProvider = ProxyProvider.objects.create(
 | 
			
		||||
            name="test",
 | 
			
		||||
            internal_host="http://localhost",
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,8 @@ import yaml
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.outposts.apps import AuthentikOutpostConfig
 | 
			
		||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
 | 
			
		||||
from authentik.outposts.tasks import outpost_local_connection
 | 
			
		||||
from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesController
 | 
			
		||||
from authentik.providers.proxy.models import ProxyProvider
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ class TestProxyKubernetes(TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        # Ensure that local connection have been created
 | 
			
		||||
        AuthentikOutpostConfig.init_local_connection()
 | 
			
		||||
        outpost_local_connection()
 | 
			
		||||
 | 
			
		||||
    def test_kubernetes_controller_static(self):
 | 
			
		||||
        """Test Kubernetes Controller"""
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user