diff --git a/authentik/outposts/controllers/base.py b/authentik/outposts/controllers/base.py index 57b9cf68b..bf30a4a7c 100644 --- a/authentik/outposts/controllers/base.py +++ b/authentik/outposts/controllers/base.py @@ -1,5 +1,5 @@ """Base Controller""" -from typing import Dict, List +from dataclasses import dataclass from structlog import get_logger from structlog.testing import capture_logs @@ -7,15 +7,26 @@ from structlog.testing import capture_logs from authentik.lib.sentry import SentryIgnoredException from authentik.outposts.models import Outpost, OutpostServiceConnection +FIELD_MANAGER = "goauthentik.io" + class ControllerException(SentryIgnoredException): """Exception raised when anything fails during controller run""" +@dataclass +class DeploymentPort: + """Info about deployment's single port.""" + + port: int + name: str + protocol: str + + class BaseController: """Base Outpost deployment controller""" - deployment_ports: Dict[str, int] + deployment_ports: list[DeploymentPort] outpost: Outpost connection: OutpostServiceConnection @@ -24,14 +35,14 @@ class BaseController: self.outpost = outpost self.connection = connection self.logger = get_logger() - self.deployment_ports = {} + self.deployment_ports = [] # pylint: disable=invalid-name def up(self): """Called by scheduled task to reconcile deployment/service/etc""" raise NotImplementedError - def up_with_logs(self) -> List[str]: + def up_with_logs(self) -> list[str]: """Call .up() but capture all log output and return it.""" with capture_logs() as logs: self.up() diff --git a/authentik/outposts/controllers/docker.py b/authentik/outposts/controllers/docker.py index f8c625f41..77c46e431 100644 --- a/authentik/outposts/controllers/docker.py +++ b/authentik/outposts/controllers/docker.py @@ -68,7 +68,10 @@ class DockerController(BaseController): "image": image_name, "name": f"authentik-proxy-{self.outpost.uuid.hex}", "detach": True, - "ports": {x: x for _, x in self.deployment_ports.items()}, + "ports": { + f"{port.port}/{port.protocol.lower()}": port.port + for port in self.deployment_ports + }, "environment": self._get_env(), "labels": self._get_labels(), } @@ -139,7 +142,10 @@ class DockerController(BaseController): def get_static_deployment(self) -> str: """Generate docker-compose yaml for proxy, version 3.5""" - ports = [f"{x}:{x}" for _, x in self.deployment_ports.items()] + ports = [ + f"{port.port}:{port.port}/{port.protocol.lower()}" + for port in self.deployment_ports + ] image_prefix = CONFIG.y("outposts.docker_image_base") compose = { "version": "3.5", diff --git a/authentik/outposts/controllers/k8s/deployment.py b/authentik/outposts/controllers/k8s/deployment.py index 1f871e249..94cb3ac68 100644 --- a/authentik/outposts/controllers/k8s/deployment.py +++ b/authentik/outposts/controllers/k8s/deployment.py @@ -18,11 +18,11 @@ from kubernetes.client import ( from authentik import __version__ from authentik.lib.config import CONFIG +from authentik.outposts.controllers.base import FIELD_MANAGER from authentik.outposts.controllers.k8s.base import ( KubernetesObjectReconciler, NeedsUpdate, ) -from authentik.outposts.controllers.kubernetes import FIELD_MANAGER from authentik.outposts.models import Outpost if TYPE_CHECKING: @@ -65,8 +65,14 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]): """Get deployment object for outpost""" # Generate V1ContainerPort objects container_ports = [] - for port_name, port in self.controller.deployment_ports.items(): - container_ports.append(V1ContainerPort(container_port=port, name=port_name)) + for port in self.controller.deployment_ports: + container_ports.append( + V1ContainerPort( + container_port=port.port, + name=port.name, + protocol=port.protocol.upper(), + ) + ) meta = self.get_object_meta(name=self.name) secret_name = f"authentik-outpost-{self.controller.outpost.uuid.hex}-api" image_prefix = CONFIG.y("outposts.docker_image_base") diff --git a/authentik/outposts/controllers/k8s/secret.py b/authentik/outposts/controllers/k8s/secret.py index 3ddedce99..d00a81d4f 100644 --- a/authentik/outposts/controllers/k8s/secret.py +++ b/authentik/outposts/controllers/k8s/secret.py @@ -4,11 +4,11 @@ from typing import TYPE_CHECKING from kubernetes.client import CoreV1Api, V1Secret +from authentik.outposts.controllers.base import FIELD_MANAGER from authentik.outposts.controllers.k8s.base import ( KubernetesObjectReconciler, NeedsUpdate, ) -from authentik.outposts.controllers.kubernetes import FIELD_MANAGER if TYPE_CHECKING: from authentik.outposts.controllers.kubernetes import KubernetesController diff --git a/authentik/outposts/controllers/k8s/service.py b/authentik/outposts/controllers/k8s/service.py index 8a1c35aab..1f03d22bb 100644 --- a/authentik/outposts/controllers/k8s/service.py +++ b/authentik/outposts/controllers/k8s/service.py @@ -3,12 +3,12 @@ from typing import TYPE_CHECKING from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec +from authentik.outposts.controllers.base import FIELD_MANAGER from authentik.outposts.controllers.k8s.base import ( KubernetesObjectReconciler, NeedsUpdate, ) from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler -from authentik.outposts.controllers.kubernetes import FIELD_MANAGER if TYPE_CHECKING: from authentik.outposts.controllers.kubernetes import KubernetesController @@ -37,8 +37,15 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]): """Get deployment object for outpost""" meta = self.get_object_meta(name=self.name) ports = [] - for port_name, port in self.controller.deployment_ports.items(): - ports.append(V1ServicePort(name=port_name, port=port)) + for port in self.controller.deployment_ports: + ports.append( + V1ServicePort( + name=port.name, + port=port.port, + protocol=port.protocol.upper(), + target_port=port.port, + ) + ) selector_labels = DeploymentReconciler(self.controller).get_pod_meta() return V1Service( metadata=meta, diff --git a/authentik/outposts/controllers/kubernetes.py b/authentik/outposts/controllers/kubernetes.py index 2daebfdc4..f75edf823 100644 --- a/authentik/outposts/controllers/kubernetes.py +++ b/authentik/outposts/controllers/kubernetes.py @@ -14,8 +14,6 @@ from authentik.outposts.controllers.k8s.secret import SecretReconciler from authentik.outposts.controllers.k8s.service import ServiceReconciler from authentik.outposts.models import KubernetesServiceConnection, Outpost -FIELD_MANAGER = "goauthentik.io" - class KubernetesController(BaseController): """Manage deployment of outpost in kubernetes""" diff --git a/authentik/providers/proxy/controllers/docker.py b/authentik/providers/proxy/controllers/docker.py index 920c76b2a..e823696ac 100644 --- a/authentik/providers/proxy/controllers/docker.py +++ b/authentik/providers/proxy/controllers/docker.py @@ -2,6 +2,7 @@ from typing import Dict from urllib.parse import urlparse +from authentik.outposts.controllers.base import DeploymentPort from authentik.outposts.controllers.docker import DockerController from authentik.outposts.models import DockerServiceConnection, Outpost from authentik.providers.proxy.models import ProxyProvider @@ -12,10 +13,10 @@ class ProxyDockerController(DockerController): def __init__(self, outpost: Outpost, connection: DockerServiceConnection): super().__init__(outpost, connection) - self.deployment_ports = { - "http": 4180, - "https": 4443, - } + self.deployment_ports = [ + DeploymentPort(4180, "http", "tcp"), + DeploymentPort(4443, "https", "tcp"), + ] def _get_labels(self) -> Dict[str, str]: hosts = [] diff --git a/authentik/providers/proxy/controllers/k8s/ingress.py b/authentik/providers/proxy/controllers/k8s/ingress.py index 029cfeec1..5a0c4c2e7 100644 --- a/authentik/providers/proxy/controllers/k8s/ingress.py +++ b/authentik/providers/proxy/controllers/k8s/ingress.py @@ -15,11 +15,11 @@ from kubernetes.client.models.networking_v1beta1_ingress_rule import ( NetworkingV1beta1IngressRule, ) +from authentik.outposts.controllers.base import FIELD_MANAGER from authentik.outposts.controllers.k8s.base import ( KubernetesObjectReconciler, NeedsUpdate, ) -from authentik.outposts.controllers.kubernetes import FIELD_MANAGER from authentik.providers.proxy.models import ProxyProvider if TYPE_CHECKING: @@ -106,7 +106,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]): NetworkingV1beta1HTTPIngressPath( backend=NetworkingV1beta1IngressBackend( service_name=self.name, - service_port=self.controller.deployment_ports["http"], + service_port="http", ), path="/", ) diff --git a/authentik/providers/proxy/controllers/kubernetes.py b/authentik/providers/proxy/controllers/kubernetes.py index 9cee34ae8..3fcc55919 100644 --- a/authentik/providers/proxy/controllers/kubernetes.py +++ b/authentik/providers/proxy/controllers/kubernetes.py @@ -1,4 +1,5 @@ """Proxy Provider Kubernetes Contoller""" +from authentik.outposts.controllers.base import DeploymentPort from authentik.outposts.controllers.kubernetes import KubernetesController from authentik.outposts.models import KubernetesServiceConnection, Outpost from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler @@ -9,9 +10,9 @@ class ProxyKubernetesController(KubernetesController): def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection): super().__init__(outpost, connection) - self.deployment_ports = { - "http": 4180, - "https": 4443, - } + self.deployment_ports = [ + DeploymentPort(4180, "http", "tcp"), + DeploymentPort(4443, "https", "tcp"), + ] self.reconcilers["ingress"] = IngressReconciler self.reconcile_order.append("ingress")