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"): |     def __init__(self, controller: "KubernetesController"): | ||||||
|         self.controller = controller |         self.controller = controller | ||||||
|         self.namespace = controller.outpost.config.kubernetes_namespace |         self.namespace = controller.outpost.config.kubernetes_namespace | ||||||
|         self.logger = get_logger() |         self.logger = get_logger().bind(type=self.__class__.__name__) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def name(self) -> str: |     def name(self) -> str: | ||||||
| @ -79,7 +79,7 @@ class KubernetesObjectReconciler(Generic[T]): | |||||||
|                 self.delete(current) |                 self.delete(current) | ||||||
|             else: |             else: | ||||||
|                 self.logger.debug("No old found, creating") |                 self.logger.debug("No old found, creating") | ||||||
|             self.logger.debug("Created") |             self.logger.debug("Creating") | ||||||
|             self.create(reference) |             self.create(reference) | ||||||
|         except NeedsUpdate: |         except NeedsUpdate: | ||||||
|             self.logger.debug("Updating") |             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.controllers.kubernetes import KubernetesController | ||||||
| from authentik.outposts.models import KubernetesServiceConnection, Outpost | from authentik.outposts.models import KubernetesServiceConnection, Outpost | ||||||
| from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler | from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler | ||||||
|  | from authentik.providers.proxy.controllers.k8s.traefik import ( | ||||||
|  |     TraefikMiddlewareReconciler, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProxyKubernetesController(KubernetesController): | class ProxyKubernetesController(KubernetesController): | ||||||
| @ -15,4 +18,6 @@ class ProxyKubernetesController(KubernetesController): | |||||||
|             DeploymentPort(4443, "https", "tcp"), |             DeploymentPort(4443, "https", "tcp"), | ||||||
|         ] |         ] | ||||||
|         self.reconcilers["ingress"] = IngressReconciler |         self.reconcilers["ingress"] = IngressReconciler | ||||||
|  |         self.reconcilers["traefik_middleware"] = TraefikMiddlewareReconciler | ||||||
|         self.reconcile_order.append("ingress") |         self.reconcile_order.append("ingress") | ||||||
|  |         self.reconcile_order.append("traefik_middleware") | ||||||
|  | |||||||
| @ -10658,8 +10658,6 @@ paths: | |||||||
|           description: '' |           description: '' | ||||||
|           schema: |           schema: | ||||||
|             $ref: '#/definitions/RedirectChallenge' |             $ref: '#/definitions/RedirectChallenge' | ||||||
|         '404': |  | ||||||
|           description: Token not found |  | ||||||
|         '400': |         '400': | ||||||
|           description: Invalid input. |           description: Invalid input. | ||||||
|           schema: |           schema: | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer