From 09f4d812b3090afb00793fbe2c3ba6ee1502784d Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 16:37:49 +0100 Subject: [PATCH 1/3] outposts: set field_manager --- authentik/outposts/controllers/k8s/deployment.py | 5 ++++- authentik/outposts/controllers/k8s/secret.py | 5 ++++- authentik/outposts/controllers/k8s/service.py | 5 ++++- authentik/outposts/controllers/kubernetes.py | 2 ++ authentik/providers/proxy/controllers/k8s/ingress.py | 5 ++++- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/authentik/outposts/controllers/k8s/deployment.py b/authentik/outposts/controllers/k8s/deployment.py index 981cade84..c241e91aa 100644 --- a/authentik/outposts/controllers/k8s/deployment.py +++ b/authentik/outposts/controllers/k8s/deployment.py @@ -22,6 +22,7 @@ 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: @@ -118,7 +119,9 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]): ) def create(self, reference: V1Deployment): - return self.api.create_namespaced_deployment(self.namespace, reference) + return self.api.create_namespaced_deployment( + self.namespace, reference, field_manager=FIELD_MANAGER + ) def delete(self, reference: V1Deployment): return self.api.delete_namespaced_deployment( diff --git a/authentik/outposts/controllers/k8s/secret.py b/authentik/outposts/controllers/k8s/secret.py index 1d65b4f78..e7fc2d3f8 100644 --- a/authentik/outposts/controllers/k8s/secret.py +++ b/authentik/outposts/controllers/k8s/secret.py @@ -8,6 +8,7 @@ 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 @@ -51,7 +52,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]): ) def create(self, reference: V1Secret): - return self.api.create_namespaced_secret(self.namespace, reference) + return self.api.create_namespaced_secret( + self.namespace, reference, field_manager=FIELD_MANAGER + ) def delete(self, reference: V1Secret): return self.api.delete_namespaced_secret( diff --git a/authentik/outposts/controllers/k8s/service.py b/authentik/outposts/controllers/k8s/service.py index b710832f1..3013a70cd 100644 --- a/authentik/outposts/controllers/k8s/service.py +++ b/authentik/outposts/controllers/k8s/service.py @@ -8,6 +8,7 @@ from authentik.outposts.controllers.k8s.base import ( 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 @@ -44,7 +45,9 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]): ) def create(self, reference: V1Service): - return self.api.create_namespaced_service(self.namespace, reference) + return self.api.create_namespaced_service( + self.namespace, reference, field_manager=FIELD_MANAGER + ) def delete(self, reference: V1Service): return self.api.delete_namespaced_service( diff --git a/authentik/outposts/controllers/kubernetes.py b/authentik/outposts/controllers/kubernetes.py index f75edf823..2daebfdc4 100644 --- a/authentik/outposts/controllers/kubernetes.py +++ b/authentik/outposts/controllers/kubernetes.py @@ -14,6 +14,8 @@ 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/k8s/ingress.py b/authentik/providers/proxy/controllers/k8s/ingress.py index 1b02a54bc..f0f643f36 100644 --- a/authentik/providers/proxy/controllers/k8s/ingress.py +++ b/authentik/providers/proxy/controllers/k8s/ingress.py @@ -19,6 +19,7 @@ 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: @@ -124,7 +125,9 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]): ) def create(self, reference: NetworkingV1beta1Ingress): - return self.api.create_namespaced_ingress(self.namespace, reference) + return self.api.create_namespaced_ingress( + self.namespace, reference, field_manager=FIELD_MANAGER + ) def delete(self, reference: NetworkingV1beta1Ingress): return self.api.delete_namespaced_ingress( From 1a292feebbe7185202885284c1ff998039eaa90f Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 16:44:27 +0100 Subject: [PATCH 2/3] outposts: always check metadata on reconcile --- authentik/outposts/controllers/k8s/base.py | 3 ++- authentik/outposts/controllers/k8s/deployment.py | 1 + authentik/outposts/controllers/k8s/secret.py | 1 + authentik/outposts/controllers/k8s/service.py | 1 + authentik/providers/proxy/controllers/k8s/ingress.py | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/authentik/outposts/controllers/k8s/base.py b/authentik/outposts/controllers/k8s/base.py index 0fbf5588a..d2f895380 100644 --- a/authentik/outposts/controllers/k8s/base.py +++ b/authentik/outposts/controllers/k8s/base.py @@ -93,7 +93,8 @@ class KubernetesObjectReconciler(Generic[T]): def reconcile(self, current: T, reference: T): """Check what operations should be done, should be raised as ReconcileTrigger""" - raise NotImplementedError + if current.metadata.annotations != reference.metadata.annotations: + raise NeedsUpdate() def create(self, reference: T): """API Wrapper to create object""" diff --git a/authentik/outposts/controllers/k8s/deployment.py b/authentik/outposts/controllers/k8s/deployment.py index c241e91aa..1f871e249 100644 --- a/authentik/outposts/controllers/k8s/deployment.py +++ b/authentik/outposts/controllers/k8s/deployment.py @@ -44,6 +44,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]): return f"authentik-outpost-{self.controller.outpost.uuid.hex}" def reconcile(self, current: V1Deployment, reference: V1Deployment): + super().reconcile(current, reference) if current.spec.replicas != reference.spec.replicas: raise NeedsUpdate() if ( diff --git a/authentik/outposts/controllers/k8s/secret.py b/authentik/outposts/controllers/k8s/secret.py index e7fc2d3f8..3ddedce99 100644 --- a/authentik/outposts/controllers/k8s/secret.py +++ b/authentik/outposts/controllers/k8s/secret.py @@ -31,6 +31,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]): return f"authentik-outpost-{self.controller.outpost.uuid.hex}-api" def reconcile(self, current: V1Secret, reference: V1Secret): + super().reconcile(current, reference) for key in reference.data.keys(): if current.data[key] != reference.data[key]: raise NeedsUpdate() diff --git a/authentik/outposts/controllers/k8s/service.py b/authentik/outposts/controllers/k8s/service.py index 3013a70cd..8a1c35aab 100644 --- a/authentik/outposts/controllers/k8s/service.py +++ b/authentik/outposts/controllers/k8s/service.py @@ -26,6 +26,7 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]): return f"authentik-outpost-{self.controller.outpost.uuid.hex}" def reconcile(self, current: V1Service, reference: V1Service): + super().reconcile(current, reference) if len(current.spec.ports) != len(reference.spec.ports): raise NeedsUpdate() for port in reference.spec.ports: diff --git a/authentik/providers/proxy/controllers/k8s/ingress.py b/authentik/providers/proxy/controllers/k8s/ingress.py index f0f643f36..029cfeec1 100644 --- a/authentik/providers/proxy/controllers/k8s/ingress.py +++ b/authentik/providers/proxy/controllers/k8s/ingress.py @@ -40,6 +40,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]): def reconcile( self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress ): + super().reconcile(current, reference) # Create a list of all expected host and tls hosts expected_hosts = [] expected_hosts_tls = [] From 22ce142cb82cda3aa26bd78ac2084c881292e69e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 17:18:01 +0100 Subject: [PATCH 3/3] outposts: include protocol in outpost deployment ports --- authentik/outposts/controllers/base.py | 19 +++++++++++++++---- authentik/outposts/controllers/docker.py | 10 ++++++++-- .../outposts/controllers/k8s/deployment.py | 12 +++++++++--- authentik/outposts/controllers/k8s/secret.py | 2 +- authentik/outposts/controllers/k8s/service.py | 13 ++++++++++--- authentik/outposts/controllers/kubernetes.py | 2 -- .../providers/proxy/controllers/docker.py | 9 +++++---- .../proxy/controllers/k8s/ingress.py | 4 ++-- .../providers/proxy/controllers/kubernetes.py | 9 +++++---- 9 files changed, 55 insertions(+), 25 deletions(-) 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")