diff --git a/helm/templates/service-account.yaml b/helm/templates/service-account.yaml index 566ee6045..09bab4057 100644 --- a/helm/templates/service-account.yaml +++ b/helm/templates/service-account.yaml @@ -26,6 +26,17 @@ rules: - "delete" - "read" - "patch" +- apiGroups: + - "extensions" + - "networking" + resources: + - "ingress" + verbs: + - "get" + - "create" + - "delete" + - "read" + - "patch" - apiGroups: - "" resources: diff --git a/passbook/providers/proxy/controllers/k8s/__init__.py b/passbook/providers/proxy/controllers/k8s/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/providers/proxy/controllers/k8s/ingress.py b/passbook/providers/proxy/controllers/k8s/ingress.py new file mode 100644 index 000000000..434cdca87 --- /dev/null +++ b/passbook/providers/proxy/controllers/k8s/ingress.py @@ -0,0 +1,107 @@ +"""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 passbook.outposts.controllers.k8s.base import ( + KubernetesObjectReconciler, + NeedsUpdate, +) +from passbook.providers.proxy.models import ProxyProvider + +if TYPE_CHECKING: + from passbook.outposts.controllers.kubernetes import KubernetesController + + +class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]): + """Kubernetes Ingress Reconciler""" + + def __init__(self, controller: "KubernetesController") -> None: + super().__init__(controller) + self.api = NetworkingV1beta1Api() + + @property + def name(self) -> str: + return f"passbook-outpost-{self.controller.outpost.name}" + + def reconcile( + self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress + ): + if len(current.spec.ports) != len(reference.spec.ports): + raise NeedsUpdate() + for port in reference.spec.ports: + if port not in current.spec.ports: + raise NeedsUpdate() + + def get_reference_object(self) -> NetworkingV1beta1Ingress: + """Get deployment object for outpost""" + meta = self.get_object_meta( + name=self.name, + annotations=self.controller.outpost.config.kubernetes_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) + rule = NetworkingV1beta1IngressRule( + host=external_host_name.hostname, + http=NetworkingV1beta1HTTPIngressRuleValue( + paths=[ + NetworkingV1beta1HTTPIngressPath( + backend=NetworkingV1beta1IngressBackend( + service_name=self.name, + service_port=self.controller.deployment_ports["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) + + 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( + f"passbook-outpost-{self.controller.outpost.name}", self.namespace + ) + + def update( + self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress + ): + return self.api.patch_namespaced_ingress( + current.metadata.name, self.namespace, reference + ) diff --git a/passbook/providers/proxy/controllers/kubernetes.py b/passbook/providers/proxy/controllers/kubernetes.py index e18a12df7..4443730db 100644 --- a/passbook/providers/proxy/controllers/kubernetes.py +++ b/passbook/providers/proxy/controllers/kubernetes.py @@ -1,6 +1,7 @@ """Proxy Provider Kubernetes Contoller""" from passbook.outposts.controllers.kubernetes import KubernetesController from passbook.outposts.models import Outpost +from passbook.providers.proxy.controllers.k8s.ingress import IngressReconciler class ProxyKubernetesController(KubernetesController): @@ -12,3 +13,5 @@ class ProxyKubernetesController(KubernetesController): "http": 4180, "https": 4443, } + self.reconcilers["ingress"] = IngressReconciler + self.reconcile_order.append("ingress")