authentik.providers.proxy.controllers.k8s.traefik_3
Kubernetes Traefik Middleware Reconciler
1"""Kubernetes Traefik Middleware Reconciler""" 2 3from dataclasses import asdict, dataclass, field 4from typing import TYPE_CHECKING 5 6from dacite.core import from_dict 7from kubernetes.client import ApiextensionsV1Api, CustomObjectsApi 8 9from authentik.outposts.controllers.base import FIELD_MANAGER 10from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler 11from authentik.outposts.controllers.k8s.triggers import NeedsUpdate 12from authentik.providers.proxy.models import ProxyMode, ProxyProvider 13 14if TYPE_CHECKING: 15 from authentik.outposts.controllers.kubernetes import KubernetesController 16 17 18@dataclass(slots=True) 19class TraefikMiddlewareSpecForwardAuth: 20 """traefik middleware forwardAuth spec""" 21 22 address: str 23 24 authResponseHeadersRegex: str = field(default="") 25 26 authResponseHeaders: list[str] = field(default_factory=list) 27 28 trustForwardHeader: bool = field(default=True) 29 30 maxResponseBodySize: int = field(default=1024 * 1024 * 4) 31 32 33@dataclass(slots=True) 34class TraefikMiddlewareSpec: 35 """Traefik middleware spec""" 36 37 forwardAuth: TraefikMiddlewareSpecForwardAuth 38 39 40@dataclass(slots=True) 41class TraefikMiddlewareMetadata: 42 """Traefik Middleware metadata""" 43 44 name: str 45 namespace: str 46 labels: dict = field(default_factory=dict) 47 48 49@dataclass(slots=True) 50class TraefikMiddleware: 51 """Traefik Middleware""" 52 53 apiVersion: str 54 kind: str 55 metadata: TraefikMiddlewareMetadata 56 spec: TraefikMiddlewareSpec 57 58 59class Traefik3MiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]): 60 """Kubernetes Traefik Middleware Reconciler""" 61 62 def __init__(self, controller: KubernetesController) -> None: 63 super().__init__(controller) 64 self.api_ex = ApiextensionsV1Api(controller.client) 65 self.api = CustomObjectsApi(controller.client) 66 self.crd_name = "middlewares.traefik.io" 67 self.crd_group = "traefik.io" 68 self.crd_version = "v1alpha1" 69 self.crd_plural = "middlewares" 70 71 @staticmethod 72 def reconciler_name() -> str: 73 return "traefik middleware" 74 75 @property 76 def noop(self) -> bool: 77 if not ProxyProvider.objects.filter( 78 outpost__in=[self.controller.outpost], 79 mode__in=[ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN], 80 ).exists(): 81 self.logger.debug("No providers with forward auth enabled.") 82 return True 83 if not self.crd_exists(): 84 self.logger.debug("CRD doesn't exist") 85 return True 86 return False 87 88 def crd_exists(self) -> bool: 89 """Check if the traefik middleware exists""" 90 return bool( 91 len( 92 self.api_ex.list_custom_resource_definition( 93 field_selector=f"metadata.name={self.crd_name}" 94 ).items 95 ) 96 ) 97 98 def reconcile(self, current: TraefikMiddleware, reference: TraefikMiddleware): 99 super().reconcile(current, reference) 100 if current.spec.forwardAuth.address != reference.spec.forwardAuth.address: 101 raise NeedsUpdate() 102 if ( 103 current.spec.forwardAuth.authResponseHeadersRegex 104 != reference.spec.forwardAuth.authResponseHeadersRegex 105 ): 106 raise NeedsUpdate() 107 # Ensure all of our headers are set, others can be added by the user. 108 if not set(current.spec.forwardAuth.authResponseHeaders).issubset( 109 reference.spec.forwardAuth.authResponseHeaders 110 ): 111 raise NeedsUpdate() 112 113 def get_reference_object(self) -> TraefikMiddleware: 114 """Get deployment object for outpost""" 115 return TraefikMiddleware( 116 apiVersion=f"{self.crd_group}/{self.crd_version}", 117 kind="Middleware", 118 metadata=TraefikMiddlewareMetadata( 119 name=self.name, 120 namespace=self.namespace, 121 labels=self.get_object_meta().labels, 122 ), 123 spec=TraefikMiddlewareSpec( 124 forwardAuth=TraefikMiddlewareSpecForwardAuth( 125 address=( 126 f"http://{self.name}.{self.namespace}:9000/" 127 "outpost.goauthentik.io/auth/traefik" 128 ), 129 authResponseHeaders=[ 130 "X-authentik-username", 131 "X-authentik-groups", 132 "X-authentik-entitlements", 133 "X-authentik-email", 134 "X-authentik-name", 135 "X-authentik-uid", 136 "X-authentik-jwt", 137 "X-authentik-meta-jwks", 138 "X-authentik-meta-outpost", 139 "X-authentik-meta-provider", 140 "X-authentik-meta-app", 141 "X-authentik-meta-version", 142 ], 143 authResponseHeadersRegex="", 144 trustForwardHeader=True, 145 maxResponseBodySize=1024 * 1024 * 4, 146 ) 147 ), 148 ) 149 150 def create(self, reference: TraefikMiddleware): 151 return self.api.create_namespaced_custom_object( 152 group=self.crd_group, 153 version=self.crd_version, 154 plural=self.crd_plural, 155 namespace=self.namespace, 156 body=asdict(reference), 157 field_manager=FIELD_MANAGER, 158 ) 159 160 def delete(self, reference: TraefikMiddleware): 161 return self.api.delete_namespaced_custom_object( 162 group=self.crd_group, 163 version=self.crd_version, 164 plural=self.crd_plural, 165 namespace=self.namespace, 166 name=self.name, 167 ) 168 169 def retrieve(self) -> TraefikMiddleware: 170 return from_dict( 171 TraefikMiddleware, 172 self.api.get_namespaced_custom_object( 173 group=self.crd_group, 174 version=self.crd_version, 175 plural=self.crd_plural, 176 namespace=self.namespace, 177 name=self.name, 178 ), 179 ) 180 181 def update(self, current: TraefikMiddleware, reference: TraefikMiddleware): 182 return self.api.patch_namespaced_custom_object( 183 group=self.crd_group, 184 version=self.crd_version, 185 plural=self.crd_plural, 186 namespace=self.namespace, 187 name=self.name, 188 body=asdict(reference), 189 field_manager=FIELD_MANAGER, 190 )
@dataclass(slots=True)
class
TraefikMiddlewareSpecForwardAuth:
19@dataclass(slots=True) 20class TraefikMiddlewareSpecForwardAuth: 21 """traefik middleware forwardAuth spec""" 22 23 address: str 24 25 authResponseHeadersRegex: str = field(default="") 26 27 authResponseHeaders: list[str] = field(default_factory=list) 28 29 trustForwardHeader: bool = field(default=True) 30 31 maxResponseBodySize: int = field(default=1024 * 1024 * 4)
traefik middleware forwardAuth spec
@dataclass(slots=True)
class
TraefikMiddlewareSpec:
34@dataclass(slots=True) 35class TraefikMiddlewareSpec: 36 """Traefik middleware spec""" 37 38 forwardAuth: TraefikMiddlewareSpecForwardAuth
Traefik middleware spec
TraefikMiddlewareSpec( forwardAuth: TraefikMiddlewareSpecForwardAuth)
forwardAuth: TraefikMiddlewareSpecForwardAuth
@dataclass(slots=True)
class
TraefikMiddlewareMetadata:
41@dataclass(slots=True) 42class TraefikMiddlewareMetadata: 43 """Traefik Middleware metadata""" 44 45 name: str 46 namespace: str 47 labels: dict = field(default_factory=dict)
Traefik Middleware metadata
@dataclass(slots=True)
class
TraefikMiddleware:
50@dataclass(slots=True) 51class TraefikMiddleware: 52 """Traefik Middleware""" 53 54 apiVersion: str 55 kind: str 56 metadata: TraefikMiddlewareMetadata 57 spec: TraefikMiddlewareSpec
Traefik Middleware
TraefikMiddleware( apiVersion: str, kind: str, metadata: TraefikMiddlewareMetadata, spec: TraefikMiddlewareSpec)
metadata: TraefikMiddlewareMetadata
spec: TraefikMiddlewareSpec
class
Traefik3MiddlewareReconciler(authentik.outposts.controllers.k8s.base.KubernetesObjectReconciler[authentik.providers.proxy.controllers.k8s.traefik_3.TraefikMiddleware]):
60class Traefik3MiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]): 61 """Kubernetes Traefik Middleware Reconciler""" 62 63 def __init__(self, controller: KubernetesController) -> None: 64 super().__init__(controller) 65 self.api_ex = ApiextensionsV1Api(controller.client) 66 self.api = CustomObjectsApi(controller.client) 67 self.crd_name = "middlewares.traefik.io" 68 self.crd_group = "traefik.io" 69 self.crd_version = "v1alpha1" 70 self.crd_plural = "middlewares" 71 72 @staticmethod 73 def reconciler_name() -> str: 74 return "traefik middleware" 75 76 @property 77 def noop(self) -> bool: 78 if not ProxyProvider.objects.filter( 79 outpost__in=[self.controller.outpost], 80 mode__in=[ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN], 81 ).exists(): 82 self.logger.debug("No providers with forward auth enabled.") 83 return True 84 if not self.crd_exists(): 85 self.logger.debug("CRD doesn't exist") 86 return True 87 return False 88 89 def crd_exists(self) -> bool: 90 """Check if the traefik middleware exists""" 91 return bool( 92 len( 93 self.api_ex.list_custom_resource_definition( 94 field_selector=f"metadata.name={self.crd_name}" 95 ).items 96 ) 97 ) 98 99 def reconcile(self, current: TraefikMiddleware, reference: TraefikMiddleware): 100 super().reconcile(current, reference) 101 if current.spec.forwardAuth.address != reference.spec.forwardAuth.address: 102 raise NeedsUpdate() 103 if ( 104 current.spec.forwardAuth.authResponseHeadersRegex 105 != reference.spec.forwardAuth.authResponseHeadersRegex 106 ): 107 raise NeedsUpdate() 108 # Ensure all of our headers are set, others can be added by the user. 109 if not set(current.spec.forwardAuth.authResponseHeaders).issubset( 110 reference.spec.forwardAuth.authResponseHeaders 111 ): 112 raise NeedsUpdate() 113 114 def get_reference_object(self) -> TraefikMiddleware: 115 """Get deployment object for outpost""" 116 return TraefikMiddleware( 117 apiVersion=f"{self.crd_group}/{self.crd_version}", 118 kind="Middleware", 119 metadata=TraefikMiddlewareMetadata( 120 name=self.name, 121 namespace=self.namespace, 122 labels=self.get_object_meta().labels, 123 ), 124 spec=TraefikMiddlewareSpec( 125 forwardAuth=TraefikMiddlewareSpecForwardAuth( 126 address=( 127 f"http://{self.name}.{self.namespace}:9000/" 128 "outpost.goauthentik.io/auth/traefik" 129 ), 130 authResponseHeaders=[ 131 "X-authentik-username", 132 "X-authentik-groups", 133 "X-authentik-entitlements", 134 "X-authentik-email", 135 "X-authentik-name", 136 "X-authentik-uid", 137 "X-authentik-jwt", 138 "X-authentik-meta-jwks", 139 "X-authentik-meta-outpost", 140 "X-authentik-meta-provider", 141 "X-authentik-meta-app", 142 "X-authentik-meta-version", 143 ], 144 authResponseHeadersRegex="", 145 trustForwardHeader=True, 146 maxResponseBodySize=1024 * 1024 * 4, 147 ) 148 ), 149 ) 150 151 def create(self, reference: TraefikMiddleware): 152 return self.api.create_namespaced_custom_object( 153 group=self.crd_group, 154 version=self.crd_version, 155 plural=self.crd_plural, 156 namespace=self.namespace, 157 body=asdict(reference), 158 field_manager=FIELD_MANAGER, 159 ) 160 161 def delete(self, reference: TraefikMiddleware): 162 return self.api.delete_namespaced_custom_object( 163 group=self.crd_group, 164 version=self.crd_version, 165 plural=self.crd_plural, 166 namespace=self.namespace, 167 name=self.name, 168 ) 169 170 def retrieve(self) -> TraefikMiddleware: 171 return from_dict( 172 TraefikMiddleware, 173 self.api.get_namespaced_custom_object( 174 group=self.crd_group, 175 version=self.crd_version, 176 plural=self.crd_plural, 177 namespace=self.namespace, 178 name=self.name, 179 ), 180 ) 181 182 def update(self, current: TraefikMiddleware, reference: TraefikMiddleware): 183 return self.api.patch_namespaced_custom_object( 184 group=self.crd_group, 185 version=self.crd_version, 186 plural=self.crd_plural, 187 namespace=self.namespace, 188 name=self.name, 189 body=asdict(reference), 190 field_manager=FIELD_MANAGER, 191 )
Kubernetes Traefik Middleware Reconciler
@staticmethod
def
reconciler_name() -> str:
A name this reconciler is identified by in the configuration
noop: bool
76 @property 77 def noop(self) -> bool: 78 if not ProxyProvider.objects.filter( 79 outpost__in=[self.controller.outpost], 80 mode__in=[ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN], 81 ).exists(): 82 self.logger.debug("No providers with forward auth enabled.") 83 return True 84 if not self.crd_exists(): 85 self.logger.debug("CRD doesn't exist") 86 return True 87 return False
Return true if this object should not be created/updated/deleted in this cluster
def
crd_exists(self) -> bool:
89 def crd_exists(self) -> bool: 90 """Check if the traefik middleware exists""" 91 return bool( 92 len( 93 self.api_ex.list_custom_resource_definition( 94 field_selector=f"metadata.name={self.crd_name}" 95 ).items 96 ) 97 )
Check if the traefik middleware exists
99 def reconcile(self, current: TraefikMiddleware, reference: TraefikMiddleware): 100 super().reconcile(current, reference) 101 if current.spec.forwardAuth.address != reference.spec.forwardAuth.address: 102 raise NeedsUpdate() 103 if ( 104 current.spec.forwardAuth.authResponseHeadersRegex 105 != reference.spec.forwardAuth.authResponseHeadersRegex 106 ): 107 raise NeedsUpdate() 108 # Ensure all of our headers are set, others can be added by the user. 109 if not set(current.spec.forwardAuth.authResponseHeaders).issubset( 110 reference.spec.forwardAuth.authResponseHeaders 111 ): 112 raise NeedsUpdate()
Check what operations should be done, should be raised as ReconcileTrigger
114 def get_reference_object(self) -> TraefikMiddleware: 115 """Get deployment object for outpost""" 116 return TraefikMiddleware( 117 apiVersion=f"{self.crd_group}/{self.crd_version}", 118 kind="Middleware", 119 metadata=TraefikMiddlewareMetadata( 120 name=self.name, 121 namespace=self.namespace, 122 labels=self.get_object_meta().labels, 123 ), 124 spec=TraefikMiddlewareSpec( 125 forwardAuth=TraefikMiddlewareSpecForwardAuth( 126 address=( 127 f"http://{self.name}.{self.namespace}:9000/" 128 "outpost.goauthentik.io/auth/traefik" 129 ), 130 authResponseHeaders=[ 131 "X-authentik-username", 132 "X-authentik-groups", 133 "X-authentik-entitlements", 134 "X-authentik-email", 135 "X-authentik-name", 136 "X-authentik-uid", 137 "X-authentik-jwt", 138 "X-authentik-meta-jwks", 139 "X-authentik-meta-outpost", 140 "X-authentik-meta-provider", 141 "X-authentik-meta-app", 142 "X-authentik-meta-version", 143 ], 144 authResponseHeadersRegex="", 145 trustForwardHeader=True, 146 maxResponseBodySize=1024 * 1024 * 4, 147 ) 148 ), 149 )
Get deployment object for outpost
151 def create(self, reference: TraefikMiddleware): 152 return self.api.create_namespaced_custom_object( 153 group=self.crd_group, 154 version=self.crd_version, 155 plural=self.crd_plural, 156 namespace=self.namespace, 157 body=asdict(reference), 158 field_manager=FIELD_MANAGER, 159 )
API Wrapper to create object
161 def delete(self, reference: TraefikMiddleware): 162 return self.api.delete_namespaced_custom_object( 163 group=self.crd_group, 164 version=self.crd_version, 165 plural=self.crd_plural, 166 namespace=self.namespace, 167 name=self.name, 168 )
API Wrapper to delete object
170 def retrieve(self) -> TraefikMiddleware: 171 return from_dict( 172 TraefikMiddleware, 173 self.api.get_namespaced_custom_object( 174 group=self.crd_group, 175 version=self.crd_version, 176 plural=self.crd_plural, 177 namespace=self.namespace, 178 name=self.name, 179 ), 180 )
API Wrapper to retrieve object
182 def update(self, current: TraefikMiddleware, reference: TraefikMiddleware): 183 return self.api.patch_namespaced_custom_object( 184 group=self.crd_group, 185 version=self.crd_version, 186 plural=self.crd_plural, 187 namespace=self.namespace, 188 name=self.name, 189 body=asdict(reference), 190 field_manager=FIELD_MANAGER, 191 )
API Wrapper to update object