Files
authentik/authentik/providers/proxy/controllers/k8s/ingress.py
Jens L dad24c03ff outposts: set cookies for a domain to authenticate an entire domain (#971)
* outposts: initial cookie domain implementation

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: add cookie domain setting

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* providers/proxy: replace forward_auth_mode with general mode

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: rebuild proxy provider form

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* providers/proxy: re-add forward_auth_mode for backwards compat

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: fix data.mode not being set

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: always set log level to debug when testing

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* providers/proxy: use new mode attribute

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* providers/proxy: only ingress /akprox on forward_domain

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* providers/proxy: fix lint error

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: fix error on ProxyProviderForm when not using proxy mode

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: fix default for outpost form's type missing

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: add additional desc for proxy modes

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outposts: fix service account permissions not always being updated

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outpost/proxy: fix redirecting to incorrect host for domain mode

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: improve error handling for network errors

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outpost: fix image naming not matching main imaeg

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outposts/proxy: fix redirects for domain mode and traefik

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: fix colour for paragraphs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: fix consent stage not showing permissions correctly

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website/docs: add domain-level docs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website/docs: fix broken links

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* outposts/proxy: remove dead code

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: fix missing id for #header-text

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-08 23:10:17 +02:00

174 lines
6.5 KiB
Python

"""Kubernetes Ingress Reconciler"""
from typing import TYPE_CHECKING
from urllib.parse import urlparse
from kubernetes.client import (
NetworkingV1beta1Api,
NetworkingV1beta1HTTPIngressPath,
NetworkingV1beta1HTTPIngressRuleValue,
NetworkingV1beta1Ingress,
NetworkingV1beta1IngressBackend,
NetworkingV1beta1IngressSpec,
NetworkingV1beta1IngressTLS,
)
from kubernetes.client.models.networking_v1beta1_ingress_rule import (
NetworkingV1beta1IngressRule,
)
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler,
NeedsUpdate,
)
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
if TYPE_CHECKING:
from authentik.outposts.controllers.kubernetes import KubernetesController
class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
"""Kubernetes Ingress Reconciler"""
def __init__(self, controller: "KubernetesController") -> None:
super().__init__(controller)
self.api = NetworkingV1beta1Api(controller.client)
def _check_annotations(self, reference: NetworkingV1beta1Ingress):
"""Check that all annotations *we* set are correct"""
for key, value in self.get_ingress_annotations().items():
if key not in reference.metadata.annotations:
raise NeedsUpdate()
if reference.metadata.annotations[key] != value:
raise NeedsUpdate()
def reconcile(
self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress
):
super().reconcile(current, reference)
self._check_annotations(reference)
# Create a list of all expected host and tls hosts
expected_hosts = []
expected_hosts_tls = []
for proxy_provider in ProxyProvider.objects.filter(
outpost__in=[self.controller.outpost],
):
proxy_provider: ProxyProvider
external_host_name = urlparse(proxy_provider.external_host)
expected_hosts.append(external_host_name.hostname)
if external_host_name.scheme == "https":
expected_hosts_tls.append(external_host_name.hostname)
expected_hosts.sort()
expected_hosts_tls.sort()
have_hosts = [rule.host for rule in reference.spec.rules]
have_hosts.sort()
have_hosts_tls = []
for tls_config in reference.spec.tls:
if tls_config:
have_hosts_tls += tls_config.hosts
have_hosts_tls.sort()
if have_hosts != expected_hosts:
raise NeedsUpdate()
if have_hosts_tls != expected_hosts_tls:
raise NeedsUpdate()
def get_ingress_annotations(self) -> dict[str, str]:
"""Get ingress annotations"""
annotations = {
# Ensure that with multiple proxy replicas deployed, the same CSRF request
# goes to the same pod
"nginx.ingress.kubernetes.io/affinity": "cookie",
"traefik.ingress.kubernetes.io/affinity": "true",
"nginx.ingress.kubernetes.io/proxy-buffers-number": "4",
"nginx.ingress.kubernetes.io/proxy-buffer-size": "16k",
}
annotations.update(
self.controller.outpost.config.kubernetes_ingress_annotations
)
return annotations
def get_reference_object(self) -> NetworkingV1beta1Ingress:
"""Get deployment object for outpost"""
meta = self.get_object_meta(
name=self.name,
annotations=self.get_ingress_annotations(),
)
rules = []
tls_hosts = []
for proxy_provider in ProxyProvider.objects.filter(
outpost__in=[self.controller.outpost],
):
proxy_provider: ProxyProvider
external_host_name = urlparse(proxy_provider.external_host)
if external_host_name.scheme == "https":
tls_hosts.append(external_host_name.hostname)
if proxy_provider.mode in [
ProxyMode.FORWARD_SINGLE,
ProxyMode.FORWARD_DOMAIN,
]:
rule = NetworkingV1beta1IngressRule(
host=external_host_name.hostname,
http=NetworkingV1beta1HTTPIngressRuleValue(
paths=[
NetworkingV1beta1HTTPIngressPath(
backend=NetworkingV1beta1IngressBackend(
service_name=self.name,
service_port="http",
),
path="/akprox",
)
]
),
)
else:
rule = NetworkingV1beta1IngressRule(
host=external_host_name.hostname,
http=NetworkingV1beta1HTTPIngressRuleValue(
paths=[
NetworkingV1beta1HTTPIngressPath(
backend=NetworkingV1beta1IngressBackend(
service_name=self.name,
service_port="http",
),
path="/",
)
]
),
)
rules.append(rule)
tls_config = None
if tls_hosts:
tls_config = NetworkingV1beta1IngressTLS(
hosts=tls_hosts,
secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name,
)
return NetworkingV1beta1Ingress(
metadata=meta,
spec=NetworkingV1beta1IngressSpec(rules=rules, tls=[tls_config]),
)
def create(self, reference: NetworkingV1beta1Ingress):
return self.api.create_namespaced_ingress(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: NetworkingV1beta1Ingress):
return self.api.delete_namespaced_ingress(
reference.metadata.name, self.namespace
)
def retrieve(self) -> NetworkingV1beta1Ingress:
return self.api.read_namespaced_ingress(self.name, self.namespace)
def update(
self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress
):
return self.api.patch_namespaced_ingress(
current.metadata.name,
self.namespace,
reference,
field_manager=FIELD_MANAGER,
)