Merge branch 'master' into version-0.14
This commit is contained in:
commit
db92178d0f
|
@ -1,5 +1,5 @@
|
||||||
"""Base Controller"""
|
"""Base Controller"""
|
||||||
from typing import Dict, List
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
from structlog.testing import capture_logs
|
from structlog.testing import capture_logs
|
||||||
|
@ -7,15 +7,26 @@ from structlog.testing import capture_logs
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
from authentik.outposts.models import Outpost, OutpostServiceConnection
|
from authentik.outposts.models import Outpost, OutpostServiceConnection
|
||||||
|
|
||||||
|
FIELD_MANAGER = "goauthentik.io"
|
||||||
|
|
||||||
|
|
||||||
class ControllerException(SentryIgnoredException):
|
class ControllerException(SentryIgnoredException):
|
||||||
"""Exception raised when anything fails during controller run"""
|
"""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:
|
class BaseController:
|
||||||
"""Base Outpost deployment controller"""
|
"""Base Outpost deployment controller"""
|
||||||
|
|
||||||
deployment_ports: Dict[str, int]
|
deployment_ports: list[DeploymentPort]
|
||||||
|
|
||||||
outpost: Outpost
|
outpost: Outpost
|
||||||
connection: OutpostServiceConnection
|
connection: OutpostServiceConnection
|
||||||
|
@ -24,14 +35,14 @@ class BaseController:
|
||||||
self.outpost = outpost
|
self.outpost = outpost
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.logger = get_logger()
|
self.logger = get_logger()
|
||||||
self.deployment_ports = {}
|
self.deployment_ports = []
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def up(self):
|
def up(self):
|
||||||
"""Called by scheduled task to reconcile deployment/service/etc"""
|
"""Called by scheduled task to reconcile deployment/service/etc"""
|
||||||
raise NotImplementedError
|
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."""
|
"""Call .up() but capture all log output and return it."""
|
||||||
with capture_logs() as logs:
|
with capture_logs() as logs:
|
||||||
self.up()
|
self.up()
|
||||||
|
|
|
@ -68,7 +68,10 @@ class DockerController(BaseController):
|
||||||
"image": image_name,
|
"image": image_name,
|
||||||
"name": f"authentik-proxy-{self.outpost.uuid.hex}",
|
"name": f"authentik-proxy-{self.outpost.uuid.hex}",
|
||||||
"detach": True,
|
"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(),
|
"environment": self._get_env(),
|
||||||
"labels": self._get_labels(),
|
"labels": self._get_labels(),
|
||||||
}
|
}
|
||||||
|
@ -139,7 +142,10 @@ class DockerController(BaseController):
|
||||||
|
|
||||||
def get_static_deployment(self) -> str:
|
def get_static_deployment(self) -> str:
|
||||||
"""Generate docker-compose yaml for proxy, version 3.5"""
|
"""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")
|
image_prefix = CONFIG.y("outposts.docker_image_base")
|
||||||
compose = {
|
compose = {
|
||||||
"version": "3.5",
|
"version": "3.5",
|
||||||
|
|
|
@ -93,7 +93,8 @@ class KubernetesObjectReconciler(Generic[T]):
|
||||||
def reconcile(self, current: T, reference: T):
|
def reconcile(self, current: T, reference: T):
|
||||||
"""Check what operations should be done, should be raised as
|
"""Check what operations should be done, should be raised as
|
||||||
ReconcileTrigger"""
|
ReconcileTrigger"""
|
||||||
raise NotImplementedError
|
if current.metadata.annotations != reference.metadata.annotations:
|
||||||
|
raise NeedsUpdate()
|
||||||
|
|
||||||
def create(self, reference: T):
|
def create(self, reference: T):
|
||||||
"""API Wrapper to create object"""
|
"""API Wrapper to create object"""
|
||||||
|
|
|
@ -18,6 +18,7 @@ from kubernetes.client import (
|
||||||
|
|
||||||
from authentik import __version__
|
from authentik import __version__
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
|
from authentik.outposts.controllers.base import FIELD_MANAGER
|
||||||
from authentik.outposts.controllers.k8s.base import (
|
from authentik.outposts.controllers.k8s.base import (
|
||||||
KubernetesObjectReconciler,
|
KubernetesObjectReconciler,
|
||||||
NeedsUpdate,
|
NeedsUpdate,
|
||||||
|
@ -43,6 +44,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
|
||||||
return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
|
return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
|
||||||
|
|
||||||
def reconcile(self, current: V1Deployment, reference: V1Deployment):
|
def reconcile(self, current: V1Deployment, reference: V1Deployment):
|
||||||
|
super().reconcile(current, reference)
|
||||||
if current.spec.replicas != reference.spec.replicas:
|
if current.spec.replicas != reference.spec.replicas:
|
||||||
raise NeedsUpdate()
|
raise NeedsUpdate()
|
||||||
if (
|
if (
|
||||||
|
@ -63,8 +65,14 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
|
||||||
"""Get deployment object for outpost"""
|
"""Get deployment object for outpost"""
|
||||||
# Generate V1ContainerPort objects
|
# Generate V1ContainerPort objects
|
||||||
container_ports = []
|
container_ports = []
|
||||||
for port_name, port in self.controller.deployment_ports.items():
|
for port in self.controller.deployment_ports:
|
||||||
container_ports.append(V1ContainerPort(container_port=port, name=port_name))
|
container_ports.append(
|
||||||
|
V1ContainerPort(
|
||||||
|
container_port=port.port,
|
||||||
|
name=port.name,
|
||||||
|
protocol=port.protocol.upper(),
|
||||||
|
)
|
||||||
|
)
|
||||||
meta = self.get_object_meta(name=self.name)
|
meta = self.get_object_meta(name=self.name)
|
||||||
secret_name = f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
|
secret_name = f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
|
||||||
image_prefix = CONFIG.y("outposts.docker_image_base")
|
image_prefix = CONFIG.y("outposts.docker_image_base")
|
||||||
|
@ -118,7 +126,9 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, reference: 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):
|
def delete(self, reference: V1Deployment):
|
||||||
return self.api.delete_namespaced_deployment(
|
return self.api.delete_namespaced_deployment(
|
||||||
|
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from kubernetes.client import CoreV1Api, V1Secret
|
from kubernetes.client import CoreV1Api, V1Secret
|
||||||
|
|
||||||
|
from authentik.outposts.controllers.base import FIELD_MANAGER
|
||||||
from authentik.outposts.controllers.k8s.base import (
|
from authentik.outposts.controllers.k8s.base import (
|
||||||
KubernetesObjectReconciler,
|
KubernetesObjectReconciler,
|
||||||
NeedsUpdate,
|
NeedsUpdate,
|
||||||
|
@ -30,6 +31,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
|
||||||
return f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
|
return f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
|
||||||
|
|
||||||
def reconcile(self, current: V1Secret, reference: V1Secret):
|
def reconcile(self, current: V1Secret, reference: V1Secret):
|
||||||
|
super().reconcile(current, reference)
|
||||||
for key in reference.data.keys():
|
for key in reference.data.keys():
|
||||||
if current.data[key] != reference.data[key]:
|
if current.data[key] != reference.data[key]:
|
||||||
raise NeedsUpdate()
|
raise NeedsUpdate()
|
||||||
|
@ -51,7 +53,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, reference: 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):
|
def delete(self, reference: V1Secret):
|
||||||
return self.api.delete_namespaced_secret(
|
return self.api.delete_namespaced_secret(
|
||||||
|
|
|
@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec
|
from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec
|
||||||
|
|
||||||
|
from authentik.outposts.controllers.base import FIELD_MANAGER
|
||||||
from authentik.outposts.controllers.k8s.base import (
|
from authentik.outposts.controllers.k8s.base import (
|
||||||
KubernetesObjectReconciler,
|
KubernetesObjectReconciler,
|
||||||
NeedsUpdate,
|
NeedsUpdate,
|
||||||
|
@ -25,6 +26,7 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
|
||||||
return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
|
return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
|
||||||
|
|
||||||
def reconcile(self, current: V1Service, reference: V1Service):
|
def reconcile(self, current: V1Service, reference: V1Service):
|
||||||
|
super().reconcile(current, reference)
|
||||||
if len(current.spec.ports) != len(reference.spec.ports):
|
if len(current.spec.ports) != len(reference.spec.ports):
|
||||||
raise NeedsUpdate()
|
raise NeedsUpdate()
|
||||||
for port in reference.spec.ports:
|
for port in reference.spec.ports:
|
||||||
|
@ -35,8 +37,15 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
|
||||||
"""Get deployment object for outpost"""
|
"""Get deployment object for outpost"""
|
||||||
meta = self.get_object_meta(name=self.name)
|
meta = self.get_object_meta(name=self.name)
|
||||||
ports = []
|
ports = []
|
||||||
for port_name, port in self.controller.deployment_ports.items():
|
for port in self.controller.deployment_ports:
|
||||||
ports.append(V1ServicePort(name=port_name, port=port))
|
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()
|
selector_labels = DeploymentReconciler(self.controller).get_pod_meta()
|
||||||
return V1Service(
|
return V1Service(
|
||||||
metadata=meta,
|
metadata=meta,
|
||||||
|
@ -44,7 +53,9 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, reference: 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):
|
def delete(self, reference: V1Service):
|
||||||
return self.api.delete_namespaced_service(
|
return self.api.delete_namespaced_service(
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from authentik.outposts.controllers.base import DeploymentPort
|
||||||
from authentik.outposts.controllers.docker import DockerController
|
from authentik.outposts.controllers.docker import DockerController
|
||||||
from authentik.outposts.models import DockerServiceConnection, Outpost
|
from authentik.outposts.models import DockerServiceConnection, Outpost
|
||||||
from authentik.providers.proxy.models import ProxyProvider
|
from authentik.providers.proxy.models import ProxyProvider
|
||||||
|
@ -12,10 +13,10 @@ class ProxyDockerController(DockerController):
|
||||||
|
|
||||||
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
|
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
|
||||||
super().__init__(outpost, connection)
|
super().__init__(outpost, connection)
|
||||||
self.deployment_ports = {
|
self.deployment_ports = [
|
||||||
"http": 4180,
|
DeploymentPort(4180, "http", "tcp"),
|
||||||
"https": 4443,
|
DeploymentPort(4443, "https", "tcp"),
|
||||||
}
|
]
|
||||||
|
|
||||||
def _get_labels(self) -> Dict[str, str]:
|
def _get_labels(self) -> Dict[str, str]:
|
||||||
hosts = []
|
hosts = []
|
||||||
|
|
|
@ -15,6 +15,7 @@ from kubernetes.client.models.networking_v1beta1_ingress_rule import (
|
||||||
NetworkingV1beta1IngressRule,
|
NetworkingV1beta1IngressRule,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from authentik.outposts.controllers.base import FIELD_MANAGER
|
||||||
from authentik.outposts.controllers.k8s.base import (
|
from authentik.outposts.controllers.k8s.base import (
|
||||||
KubernetesObjectReconciler,
|
KubernetesObjectReconciler,
|
||||||
NeedsUpdate,
|
NeedsUpdate,
|
||||||
|
@ -39,6 +40,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
|
||||||
def reconcile(
|
def reconcile(
|
||||||
self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress
|
self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress
|
||||||
):
|
):
|
||||||
|
super().reconcile(current, reference)
|
||||||
# Create a list of all expected host and tls hosts
|
# Create a list of all expected host and tls hosts
|
||||||
expected_hosts = []
|
expected_hosts = []
|
||||||
expected_hosts_tls = []
|
expected_hosts_tls = []
|
||||||
|
@ -104,7 +106,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
|
||||||
NetworkingV1beta1HTTPIngressPath(
|
NetworkingV1beta1HTTPIngressPath(
|
||||||
backend=NetworkingV1beta1IngressBackend(
|
backend=NetworkingV1beta1IngressBackend(
|
||||||
service_name=self.name,
|
service_name=self.name,
|
||||||
service_port=self.controller.deployment_ports["http"],
|
service_port="http",
|
||||||
),
|
),
|
||||||
path="/",
|
path="/",
|
||||||
)
|
)
|
||||||
|
@ -124,7 +126,9 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, reference: 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):
|
def delete(self, reference: NetworkingV1beta1Ingress):
|
||||||
return self.api.delete_namespaced_ingress(
|
return self.api.delete_namespaced_ingress(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Proxy Provider Kubernetes Contoller"""
|
"""Proxy Provider Kubernetes Contoller"""
|
||||||
|
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
|
||||||
|
@ -9,9 +10,9 @@ class ProxyKubernetesController(KubernetesController):
|
||||||
|
|
||||||
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
|
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
|
||||||
super().__init__(outpost, connection)
|
super().__init__(outpost, connection)
|
||||||
self.deployment_ports = {
|
self.deployment_ports = [
|
||||||
"http": 4180,
|
DeploymentPort(4180, "http", "tcp"),
|
||||||
"https": 4443,
|
DeploymentPort(4443, "https", "tcp"),
|
||||||
}
|
]
|
||||||
self.reconcilers["ingress"] = IngressReconciler
|
self.reconcilers["ingress"] = IngressReconciler
|
||||||
self.reconcile_order.append("ingress")
|
self.reconcile_order.append("ingress")
|
||||||
|
|
Reference in New Issue