diff --git a/modules/kubernetes/main.tf b/modules/kubernetes/main.tf index 28ca3ce1..1a68725e 100644 --- a/modules/kubernetes/main.tf +++ b/modules/kubernetes/main.tf @@ -51,10 +51,7 @@ variable "headscale_config" {} variable "headscale_acl" {} variable "immich_postgresql_password" {} variable "immich_frame_api_key" {} -variable "ingress_honeypotapikey" {} variable "ingress_crowdsec_api_key" {} -variable "ingress_crowdsec_captcha_secret_key" {} -variable "ingress_crowdsec_captcha_site_key" {} variable "crowdsec_enroll_key" { type = string } variable "crowdsec_db_password" { type = string } variable "crowdsec_dash_api_key" { type = string } @@ -136,9 +133,9 @@ variable "defcon_level" { } locals { defcon_modules = { - 1 : ["wireguard", "technitium", "headscale", "nginx-ingress", "xray", "authentik", "cloudflare", "authelia", "monitoring"], # Critical connectivity services - 2 : ["vaultwarden", "redis", "immich", "nvidia", "metrics-server", "uptime-kuma", "crowdsec", "kyverno"], # Storage and other db services - 3 : ["k8s-dashboard", "reverse-proxy"], # Cluster admin services + 1 : ["wireguard", "technitium", "headscale", "traefik", "xray", "authentik", "cloudflare", "authelia", "monitoring"], # Critical connectivity services + 2 : ["vaultwarden", "redis", "immich", "nvidia", "metrics-server", "uptime-kuma", "crowdsec", "kyverno"], # Storage and other db services + 3 : ["reverse-proxy"], # Cluster admin services (k8s-dashboard chart repo still 404) 4 : [ "mailserver", "shadowsocks", "webhook_handler", "tuya-bridge", "dawarich", "owntracks", "nextcloud", "calibre", "onlyoffice", "f1-stream", "rybbit", "isponsorblocktv", "actualbudget" @@ -170,7 +167,7 @@ resource "null_resource" "core_services" { # List all the core modules that must be provisioned first depends_on = [ module.metallb, module.dbaas, module.technitium, module.vaultwarden, module.reverse-proxy, - module.redis, module.nginx-ingress, module.crowdsec, module.cloudflared, module.metrics-server, module.authentik, + module.redis, module.traefik, module.crowdsec, module.cloudflared, module.metrics-server, module.authentik, module.nvidia, ] } @@ -569,14 +566,12 @@ module "immich" { depends_on = [null_resource.core_services] } -module "nginx-ingress" { - source = "./nginx-ingress" - tier = local.tiers.core - for_each = contains(local.active_modules, "nginx-ingress") ? { nginx-ingress = true } : {} - honeypotapikey = var.ingress_honeypotapikey - crowdsec_api_key = var.ingress_crowdsec_api_key - crowdsec_captcha_secret_key = var.ingress_crowdsec_captcha_secret_key - crowdsec_captcha_site_key = var.ingress_crowdsec_captcha_site_key +module "traefik" { + source = "./traefik" + tier = local.tiers.core + for_each = contains(local.active_modules, "traefik") ? { traefik = true } : {} + crowdsec_api_key = var.ingress_crowdsec_api_key + tls_secret_name = var.tls_secret_name } module "crowdsec" { diff --git a/modules/kubernetes/traefik/main.tf b/modules/kubernetes/traefik/main.tf new file mode 100644 index 00000000..539efdbd --- /dev/null +++ b/modules/kubernetes/traefik/main.tf @@ -0,0 +1,227 @@ +variable "tier" { type = string } +variable "crowdsec_api_key" { type = string } +variable "tls_secret_name" {} + +resource "kubernetes_namespace" "traefik" { + metadata { + name = "traefik" + labels = { + "app.kubernetes.io/name" = "traefik" + "app.kubernetes.io/instance" = "traefik" + tier = var.tier + } + } +} + +resource "helm_release" "traefik" { + namespace = kubernetes_namespace.traefik.metadata[0].name + create_namespace = false + name = "traefik" + repository = "https://traefik.github.io/charts" + chart = "traefik" + atomic = true + timeout = 600 + + values = [yamlencode({ + deployment = { + replicas = 3 + podAnnotations = { + "diun.enable" = "true" + "diun.include_tags" = "^v\\d+(?:\\.\\d+)?(?:\\.\\d+)?.*$" + } + } + + updateStrategy = { + type = "RollingUpdate" + rollingUpdate = { + maxUnavailable = 1 + maxSurge = 2 + } + } + + ingressClass = { + enabled = true + isDefaultClass = true + } + + providers = { + kubernetesIngress = { + enabled = true + allowExternalNameServices = true + publishedService = { enabled = true } + } + kubernetesCRD = { + enabled = true + allowExternalNameServices = true + } + } + + # Enable dashboard API (accessible on port 8080 internally) + api = { + insecure = true + } + + # Entrypoints + ports = { + web = { + port = 8000 + exposedPort = 80 + protocol = "TCP" + http = { + redirections = { + entryPoint = { + to = "websecure" + scheme = "https" + } + } + } + } + websecure = { + port = 8443 + exposedPort = 443 + protocol = "TCP" + http = { + tls = { + enabled = true + } + } + } + dns-udp = { + port = 5353 + exposedPort = 53 + protocol = "UDP" + } + } + + service = { + type = "LoadBalancer" + annotations = { + # Temporary IP during migration; will move to nginx's 10.0.20.202 once nginx is removed + "metallb.universe.tf/loadBalancerIPs" = "10.0.20.202" + } + spec = { + externalTrafficPolicy = "Local" + } + } + + # Plugins + experimental = { + plugins = { + crowdsec-bouncer = { + moduleName = "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" + version = "v1.4.2" + } + rewritebody = { + moduleName = "github.com/traefik/plugin-rewritebody" + version = "v0.3.1" + } + } + } + + # Prometheus metrics + metrics = { + prometheus = { + entryPoint = "metrics" + addEntryPointsLabels = true + addServicesLabels = true + addRoutersLabels = true + } + } + + # Access logs + logs = { + access = { + enabled = true + } + } + + additionalArguments = [ + "--global.checknewversion=false", + "--global.sendanonymoususage=false", + # Increase timeouts for services like Immich + "--serversTransport.forwardingTimeouts.dialTimeout=60s", + "--serversTransport.forwardingTimeouts.responseHeaderTimeout=0s", + "--serversTransport.forwardingTimeouts.idleConnTimeout=90s", + # Use forwarded headers from trusted proxies + "--entryPoints.websecure.forwardedHeaders.insecure=true", + "--entryPoints.web.forwardedHeaders.insecure=true", + ] + + resources = { + requests = { + cpu = "100m" + memory = "128Mi" + } + } + + nodeSelector = { + "kubernetes.io/os" = "linux" + } + + tolerations = [] + })] +} + +# DNS UDP passthrough to Technitium +resource "kubernetes_manifest" "dns_udp_ingressroute" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "IngressRouteUDP" + metadata = { + name = "dns-udp" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + entryPoints = ["dns-udp"] + routes = [{ + services = [{ + name = "technitium-dns" + namespace = "technitium" + port = 53 + }] + }] + } + } + + depends_on = [helm_release.traefik] +} + +# Dashboard resources +module "tls_secret" { + source = "../setup_tls_secret" + namespace = kubernetes_namespace.traefik.metadata[0].name + tls_secret_name = var.tls_secret_name +} + +resource "kubernetes_service" "traefik_dashboard" { + metadata { + name = "traefik-dashboard" + namespace = kubernetes_namespace.traefik.metadata[0].name + labels = { + "app" = "traefik-dashboard" + } + } + + spec { + selector = { + "app.kubernetes.io/name" = "traefik" + } + port { + name = "http" + port = 8080 + target_port = 8080 + protocol = "TCP" + } + } +} + +module "ingress" { + source = "../ingress_factory" + namespace = kubernetes_namespace.traefik.metadata[0].name + name = "traefik" + service_name = "traefik-dashboard" + host = "traefik" + port = 8080 + tls_secret_name = var.tls_secret_name + protected = true +} diff --git a/terraform.tfvars b/terraform.tfvars index 0e0d9fe0..2baa4fce 100644 Binary files a/terraform.tfvars and b/terraform.tfvars differ