outposts: create traefikmiddleware if forwardAuth is enabled
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -42,7 +42,7 @@ class KubernetesObjectReconciler(Generic[T]): | ||||
|     def __init__(self, controller: "KubernetesController"): | ||||
|         self.controller = controller | ||||
|         self.namespace = controller.outpost.config.kubernetes_namespace | ||||
|         self.logger = get_logger() | ||||
|         self.logger = get_logger().bind(type=self.__class__.__name__) | ||||
|  | ||||
|     @property | ||||
|     def name(self) -> str: | ||||
| @ -79,7 +79,7 @@ class KubernetesObjectReconciler(Generic[T]): | ||||
|                 self.delete(current) | ||||
|             else: | ||||
|                 self.logger.debug("No old found, creating") | ||||
|             self.logger.debug("Created") | ||||
|             self.logger.debug("Creating") | ||||
|             self.create(reference) | ||||
|         except NeedsUpdate: | ||||
|             self.logger.debug("Updating") | ||||
|  | ||||
							
								
								
									
										160
									
								
								authentik/providers/proxy/controllers/k8s/traefik.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								authentik/providers/proxy/controllers/k8s/traefik.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,160 @@ | ||||
| """Kubernetes Traefik Middleware Reconciler""" | ||||
| from dataclasses import asdict, dataclass, field | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from dacite import from_dict | ||||
| from kubernetes.client import ApiextensionsV1Api, CustomObjectsApi | ||||
|  | ||||
| from authentik.outposts.controllers.base import FIELD_MANAGER | ||||
| from authentik.outposts.controllers.k8s.base import ( | ||||
|     Disabled, | ||||
|     KubernetesObjectReconciler, | ||||
|     NeedsUpdate, | ||||
| ) | ||||
| from authentik.providers.proxy.models import ProxyProvider | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from authentik.outposts.controllers.kubernetes import KubernetesController | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class TraefikMiddlewareSpecForwardAuth: | ||||
|     """traefik middleware forwardAuth spec""" | ||||
|  | ||||
|     address: str | ||||
|     # pylint: disable=invalid-name | ||||
|     authResponseHeaders: list[str] | ||||
|     # pylint: disable=invalid-name | ||||
|     trustForwardHeader: bool | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class TraefikMiddlewareSpec: | ||||
|     """Traefik middleware spec""" | ||||
|  | ||||
|     # pylint: disable=invalid-name | ||||
|     forwardAuth: TraefikMiddlewareSpecForwardAuth | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class TraefikMiddlewareMetadata: | ||||
|     """Traefik Middleware metadata""" | ||||
|  | ||||
|     name: str | ||||
|     namespace: str | ||||
|     labels: dict = field(default_factory=dict) | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class TraefikMiddleware: | ||||
|     """Traefik Middleware""" | ||||
|  | ||||
|     # pylint: disable=invalid-name | ||||
|     apiVersion: str | ||||
|     kind: str | ||||
|     metadata: TraefikMiddlewareMetadata | ||||
|     spec: TraefikMiddlewareSpec | ||||
|  | ||||
|  | ||||
| CRD_NAME = "middlewares.traefik.containo.us" | ||||
| CRD_GROUP = "traefik.containo.us" | ||||
| CRD_VERSION = "v1alpha1" | ||||
| CRD_PLURAL = "middlewares" | ||||
|  | ||||
|  | ||||
| class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]): | ||||
|     """Kubernetes Traefik Middleware Reconciler""" | ||||
|  | ||||
|     def __init__(self, controller: "KubernetesController") -> None: | ||||
|         super().__init__(controller) | ||||
|         self.api_ex = ApiextensionsV1Api(controller.client) | ||||
|         self.api = CustomObjectsApi(controller.client) | ||||
|  | ||||
|     def _crd_exists(self) -> bool: | ||||
|         """Check if the traefik middleware exists""" | ||||
|         return bool( | ||||
|             len( | ||||
|                 self.api_ex.list_custom_resource_definition( | ||||
|                     field_selector=f"metadata.name={CRD_NAME}" | ||||
|                 ).items | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     def reconcile(self, current: TraefikMiddleware, reference: TraefikMiddleware): | ||||
|         super().reconcile(current, reference) | ||||
|         if current.spec.forwardAuth.address != reference.spec.forwardAuth.address: | ||||
|             raise NeedsUpdate() | ||||
|  | ||||
|     def get_reference_object(self) -> TraefikMiddleware: | ||||
|         """Get deployment object for outpost""" | ||||
|         if not ProxyProvider.objects.filter( | ||||
|             outpost__in=[self.controller.outpost], | ||||
|             forward_auth_mode=True, | ||||
|         ).exists(): | ||||
|             raise Disabled() | ||||
|         if not self._crd_exists(): | ||||
|             raise Disabled() | ||||
|         return TraefikMiddleware( | ||||
|             apiVersion=f"{CRD_GROUP}/{CRD_VERSION}", | ||||
|             kind="Middleware", | ||||
|             metadata=TraefikMiddlewareMetadata( | ||||
|                 name=self.name, | ||||
|                 namespace=self.namespace, | ||||
|                 labels=self.get_object_meta().labels, | ||||
|             ), | ||||
|             spec=TraefikMiddlewareSpec( | ||||
|                 forwardAuth=TraefikMiddlewareSpecForwardAuth( | ||||
|                     address=f"http://{self.name}:4180/akprox/auth?traefik", | ||||
|                     authResponseHeaders=[ | ||||
|                         "Set-Cookie", | ||||
|                         "X-Auth-Username", | ||||
|                         "X-Forwarded-Email", | ||||
|                         "X-Forwarded-Preferred-Username", | ||||
|                         "X-Forwarded-User", | ||||
|                     ], | ||||
|                     trustForwardHeader=True, | ||||
|                 ) | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     def create(self, reference: TraefikMiddleware): | ||||
|         return self.api.create_namespaced_custom_object( | ||||
|             group=CRD_GROUP, | ||||
|             version=CRD_VERSION, | ||||
|             plural=CRD_PLURAL, | ||||
|             namespace=self.namespace, | ||||
|             body=asdict(reference), | ||||
|             field_manager=FIELD_MANAGER, | ||||
|         ) | ||||
|  | ||||
|     def delete(self, reference: TraefikMiddleware): | ||||
|         return self.api.delete_namespaced_custom_object( | ||||
|             group=CRD_GROUP, | ||||
|             version=CRD_VERSION, | ||||
|             namespace=self.namespace, | ||||
|             plural=CRD_PLURAL, | ||||
|             name=self.name, | ||||
|         ) | ||||
|  | ||||
|     def retrieve(self) -> TraefikMiddleware: | ||||
|         return from_dict( | ||||
|             TraefikMiddleware, | ||||
|             self.api.get_namespaced_custom_object( | ||||
|                 group=CRD_GROUP, | ||||
|                 version=CRD_VERSION, | ||||
|                 namespace=self.namespace, | ||||
|                 plural=CRD_PLURAL, | ||||
|                 name=self.name, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     def update(self, current: TraefikMiddleware, reference: TraefikMiddleware): | ||||
|         return self.api.patch_namespaced_custom_object( | ||||
|             group=CRD_GROUP, | ||||
|             version=CRD_VERSION, | ||||
|             namespace=self.namespace, | ||||
|             plural=CRD_PLURAL, | ||||
|             name=self.name, | ||||
|             body=asdict(reference), | ||||
|             field_manager=FIELD_MANAGER, | ||||
|         ) | ||||
| @ -3,6 +3,9 @@ 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 | ||||
| from authentik.providers.proxy.controllers.k8s.traefik import ( | ||||
|     TraefikMiddlewareReconciler, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class ProxyKubernetesController(KubernetesController): | ||||
| @ -15,4 +18,6 @@ class ProxyKubernetesController(KubernetesController): | ||||
|             DeploymentPort(4443, "https", "tcp"), | ||||
|         ] | ||||
|         self.reconcilers["ingress"] = IngressReconciler | ||||
|         self.reconcilers["traefik_middleware"] = TraefikMiddlewareReconciler | ||||
|         self.reconcile_order.append("ingress") | ||||
|         self.reconcile_order.append("traefik_middleware") | ||||
|  | ||||
| @ -10658,8 +10658,6 @@ paths: | ||||
|           description: '' | ||||
|           schema: | ||||
|             $ref: '#/definitions/RedirectChallenge' | ||||
|         '404': | ||||
|           description: Token not found | ||||
|         '400': | ||||
|           description: Invalid input. | ||||
|           schema: | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer