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

TraefikMiddlewareSpecForwardAuth( address: str, authResponseHeadersRegex: str = '', authResponseHeaders: list[str] = <factory>, trustForwardHeader: bool = True, maxResponseBodySize: int = 4194304)
address: str
authResponseHeadersRegex: str
authResponseHeaders: list[str]
trustForwardHeader: bool
maxResponseBodySize: int
@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)
@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

TraefikMiddlewareMetadata(name: str, namespace: str, labels: dict = <factory>)
name: str
namespace: str
labels: dict
@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)
apiVersion: str
kind: str
 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

api_ex
api
crd_name
crd_group
crd_version
crd_plural
@staticmethod
def reconciler_name() -> str:
72    @staticmethod
73    def reconciler_name() -> str:
74        return "traefik middleware"

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

def reconcile( self, current: TraefikMiddleware, reference: TraefikMiddleware):
 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

def get_reference_object( self) -> TraefikMiddleware:
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

def create( self, reference: TraefikMiddleware):
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

def delete( self, reference: TraefikMiddleware):
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

def retrieve( self) -> TraefikMiddleware:
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

def update( self, current: TraefikMiddleware, reference: TraefikMiddleware):
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