From b36932f9a36042166edd8c0adc30dd763c78375a Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 7 Feb 2026 13:25:49 +0000 Subject: [PATCH] Migrate all service modules from nginx-ingress to Traefik - Remove nginx-specific ingress variables (use_proxy_protocol, proxy_timeout, additional_configuration_snippet) - Update ingress annotations to use Traefik middleware CRDs - Delete nginx-ingress module (replaced by traefik) - Add new traefik middleware.tf for shared middleware definitions - Update service modules to work with new ingress_factory interface --- .../kubernetes/actualbudget/factory/main.tf | 6 +- modules/kubernetes/affine/main.tf | 3 - modules/kubernetes/audiobookshelf/main.tf | 6 +- modules/kubernetes/authentik/main.tf | 4 +- modules/kubernetes/blog/main.tf | 41 +- modules/kubernetes/calibre/main.tf | 8 +- modules/kubernetes/crowdsec/main.tf | 26 +- modules/kubernetes/dawarich/main.tf | 13 +- modules/kubernetes/discount-bandit/main.tf | 4 +- modules/kubernetes/ebook2audiobook/main.tf | 1 - modules/kubernetes/excalidraw/main.tf | 3 - modules/kubernetes/f1-stream/main.tf | 6 +- modules/kubernetes/finance_app/main.tf | 9 +- modules/kubernetes/forgejo/main.tf | 3 - modules/kubernetes/frigate/main.tf | 28 +- modules/kubernetes/hackmd/main.tf | 3 - modules/kubernetes/home_assistant/main.tf | 8 +- modules/kubernetes/homepage/values.yaml | 2 +- modules/kubernetes/immich/main.tf | 85 +- modules/kubernetes/jellyfin/main.tf | 5 +- modules/kubernetes/kafka/main.tf | 8 +- .../monitoring/grafana_chart_values.yaml | 10 +- modules/kubernetes/monitoring/main.tf | 50 +- .../monitoring/prometheus_chart_values.tpl | 30 +- modules/kubernetes/n8n/main.tf | 3 - modules/kubernetes/nextcloud/main.tf | 20 +- modules/kubernetes/nginx-ingress/main.tf | 910 ------------------ .../kubernetes/nginx-ingress/modsecurity.conf | 289 ------ modules/kubernetes/oauth-proxy/main.tf | 4 +- modules/kubernetes/ollama/main.tf | 1 - modules/kubernetes/openid_help_page/main.tf | 4 +- modules/kubernetes/owntracks/main.tf | 20 +- modules/kubernetes/paperless-ngx/main.tf | 4 - modules/kubernetes/pihole/main.tf | 7 +- modules/kubernetes/plotting-book/main.tf | 6 +- modules/kubernetes/privatebin/main.tf | 16 +- .../kubernetes/real-estate-crawler/main.tf | 53 +- .../kubernetes/reverse_proxy/factory/main.tf | 163 ++-- modules/kubernetes/reverse_proxy/main.tf | 40 +- modules/kubernetes/rybbit/main.tf | 53 +- modules/kubernetes/send/main.tf | 6 +- .../kubernetes/servarr/qbittorrent/main.tf | 3 - modules/kubernetes/servarr/readarr/main.tf | 6 +- modules/kubernetes/traefik/middleware.tf | 177 ++++ modules/kubernetes/tuya-bridge/main.tf | 9 - modules/kubernetes/uptime-kuma/main.tf | 5 +- modules/kubernetes/vikunja/main.tf | 4 +- modules/kubernetes/webhook_handler/main.tf | 4 +- modules/kubernetes/xray/main.tf | 20 +- modules/kubernetes/youtube_dl/main.tf | 8 - 50 files changed, 516 insertions(+), 1681 deletions(-) delete mode 100644 modules/kubernetes/nginx-ingress/main.tf delete mode 100644 modules/kubernetes/nginx-ingress/modsecurity.conf create mode 100644 modules/kubernetes/traefik/middleware.tf diff --git a/modules/kubernetes/actualbudget/factory/main.tf b/modules/kubernetes/actualbudget/factory/main.tf index 315fed2f..d76a7f94 100644 --- a/modules/kubernetes/actualbudget/factory/main.tf +++ b/modules/kubernetes/actualbudget/factory/main.tf @@ -93,11 +93,7 @@ module "ingress" { namespace = "actualbudget" name = "budget-${var.name}" tls_secret_name = var.tls_secret_name - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "0", - "nginx.ingress.kubernetes.io/client-max-body-size" : "0" - } - rybbit_site_id = "3e6b6b68088a" + rybbit_site_id = "3e6b6b68088a" } diff --git a/modules/kubernetes/affine/main.tf b/modules/kubernetes/affine/main.tf index 73020279..2e1a482b 100644 --- a/modules/kubernetes/affine/main.tf +++ b/modules/kubernetes/affine/main.tf @@ -211,7 +211,4 @@ module "ingress" { name = "affine" tls_secret_name = var.tls_secret_name max_body_size = "500m" - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "500m" - } } diff --git a/modules/kubernetes/audiobookshelf/main.tf b/modules/kubernetes/audiobookshelf/main.tf index 62d1207b..2a42eb2a 100644 --- a/modules/kubernetes/audiobookshelf/main.tf +++ b/modules/kubernetes/audiobookshelf/main.tf @@ -129,10 +129,6 @@ module "ingress" { namespace = kubernetes_namespace.audiobookshelf.metadata[0].name name = "audiobookshelf" tls_secret_name = var.tls_secret_name - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "0", - "nginx.ingress.kubernetes.io/client-max-body-size" : "0" - } - rybbit_site_id = "b38fda4285df" + rybbit_site_id = "b38fda4285df" } diff --git a/modules/kubernetes/authentik/main.tf b/modules/kubernetes/authentik/main.tf index 483e3ef0..71730e21 100644 --- a/modules/kubernetes/authentik/main.tf +++ b/modules/kubernetes/authentik/main.tf @@ -40,11 +40,13 @@ resource "kubernetes_ingress_v1" "authentik" { name = "authentik" namespace = kubernetes_namespace.authentik.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["authentik.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/blog/main.tf b/modules/kubernetes/blog/main.tf index eef7860e..afe3fb73 100644 --- a/modules/kubernetes/blog/main.tf +++ b/modules/kubernetes/blog/main.tf @@ -113,26 +113,13 @@ resource "kubernetes_ingress_v1" "blog" { name = "blog-ingress" namespace = kubernetes_namespace.website.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/configuration-snippet" = <<-EOT - # Only modify HTML - sub_filter_types text/html; - sub_filter_once off; - - # Disable compression so sub_filter works - proxy_set_header Accept-Encoding ""; - - # Inject analytics before - sub_filter '' ' - - '; - EOT + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,website-rybbit-analytics@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["viktorbarzin.me"] secret_name = var.tls_secret_name @@ -171,3 +158,25 @@ resource "kubernetes_ingress_v1" "blog" { } } } + +# Rybbit analytics middleware for blog +resource "kubernetes_manifest" "rybbit_analytics" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "rybbit-analytics" + namespace = kubernetes_namespace.website.metadata[0].name + } + spec = { + plugin = { + rewritebody = { + rewrites = [{ + regex = "" + replacement = "" + }] + } + } + } + } +} diff --git a/modules/kubernetes/calibre/main.tf b/modules/kubernetes/calibre/main.tf index 32594172..daa5fceb 100644 --- a/modules/kubernetes/calibre/main.tf +++ b/modules/kubernetes/calibre/main.tf @@ -224,8 +224,6 @@ module "ingress" { name = "calibre" tls_secret_name = var.tls_secret_name extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "5000m" - "gethomepage.dev/enabled" = "true" "gethomepage.dev/description" = "Book library" # gethomepage.dev/group: Media @@ -239,10 +237,8 @@ module "ingress" { # gethomepage.dev/weight: 10 # optional # gethomepage.dev/instance: "public" # optional } - rybbit_site_id = "17a5c7fbb077" - additional_configuration_snippet = <<-EOF - more_set_headers "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' https://rybbit.viktorbarzin.me"; - EOF + rybbit_site_id = "17a5c7fbb077" + custom_content_security_policy = "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://rybbit.viktorbarzin.me" } # Stacks - Anna's Archive Download Manager diff --git a/modules/kubernetes/crowdsec/main.tf b/modules/kubernetes/crowdsec/main.tf index e914beb7..e272bcd5 100644 --- a/modules/kubernetes/crowdsec/main.tf +++ b/modules/kubernetes/crowdsec/main.tf @@ -181,25 +181,13 @@ resource "kubernetes_service" "crowdsec-web" { } } module "ingress" { - source = "../ingress_factory" - namespace = kubernetes_namespace.crowdsec.metadata[0].name - name = "crowdsec-web" - protected = true - tls_secret_name = var.tls_secret_name - extra_annotations = { - # "crowdsec.io/bouncer-mode" : "bypass" - "nginx.ingress.kubernetes.io/server-snippet" : <<-EOF - # --- Disable CrowdSec for this host --- - set $crowdsec_bypass 1; - access_by_lua_block { - -- Skip calling CrowdSec for this server - if ngx.var.crowdsec_bypass == "1" then - return - end - } - EOF - } - rybbit_site_id = "d09137795ccc" + source = "../ingress_factory" + namespace = kubernetes_namespace.crowdsec.metadata[0].name + name = "crowdsec-web" + protected = true + tls_secret_name = var.tls_secret_name + exclude_crowdsec = true + rybbit_site_id = "d09137795ccc" } # CronJob to import public blocklists into CrowdSec diff --git a/modules/kubernetes/dawarich/main.tf b/modules/kubernetes/dawarich/main.tf index 215b18ef..aeb345e6 100644 --- a/modules/kubernetes/dawarich/main.tf +++ b/modules/kubernetes/dawarich/main.tf @@ -323,16 +323,5 @@ module "ingress" { namespace = kubernetes_namespace.dawarich.metadata[0].name name = "dawarich" tls_secret_name = var.tls_secret_name - extra_annotations = { - "nginx.ingress.kubernetes.io/limit-connections" : 100 - "nginx.ingress.kubernetes.io/limit-rps" : 50 - "nginx.ingress.kubernetes.io/limit-rpm" : 1000 - "nginx.ingress.kubernetes.io/limit-burst-multiplier" : 500 - "nginx.ingress.kubernetes.io/limit-rate-after" : 1000 - "nginx.ingress.kubernetes.io/configuration-snippet" = <<-EOF - limit_req_status 429; - limit_conn_status 429; - EOF - } - rybbit_site_id = "0abfd409f2fb" + rybbit_site_id = "0abfd409f2fb" } diff --git a/modules/kubernetes/discount-bandit/main.tf b/modules/kubernetes/discount-bandit/main.tf index 1d605a84..b3e4b140 100644 --- a/modules/kubernetes/discount-bandit/main.tf +++ b/modules/kubernetes/discount-bandit/main.tf @@ -103,11 +103,13 @@ resource "kubernetes_ingress_v1" "discount-bandit" { name = "discount-bandit" namespace = kubernetes_namespace.discount-bandit.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["discount.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/ebook2audiobook/main.tf b/modules/kubernetes/ebook2audiobook/main.tf index cb184db6..00820a7a 100644 --- a/modules/kubernetes/ebook2audiobook/main.tf +++ b/modules/kubernetes/ebook2audiobook/main.tf @@ -405,6 +405,5 @@ module "audiblez-web-ingress" { tls_secret_name = var.tls_secret_name protected = true max_body_size = "500m" # Allow large EPUB uploads - proxy_timeout = 3600 # Long timeout for conversions } diff --git a/modules/kubernetes/excalidraw/main.tf b/modules/kubernetes/excalidraw/main.tf index c07a06e7..99d6b14a 100644 --- a/modules/kubernetes/excalidraw/main.tf +++ b/modules/kubernetes/excalidraw/main.tf @@ -103,7 +103,4 @@ module "ingress" { name = "draw" tls_secret_name = var.tls_secret_name protected = true - extra_annotations = { - "nginx.ingress.kubernetes.io/auth-response-headers" = "X-authentik-username,X-authentik-email,X-authentik-name" - } } diff --git a/modules/kubernetes/f1-stream/main.tf b/modules/kubernetes/f1-stream/main.tf index 24caf9da..dd662925 100644 --- a/modules/kubernetes/f1-stream/main.tf +++ b/modules/kubernetes/f1-stream/main.tf @@ -87,9 +87,5 @@ module "ingress" { namespace = kubernetes_namespace.f1-stream.metadata[0].name name = "f1" tls_secret_name = var.tls_secret_name - extra_annotations = { - "nginx.ingress.kubernetes.io/force-ssl-redirect" : "false" - "nginx.ingress.kubernetes.io/ssl-redirect" : "false" - } - rybbit_site_id = "7e69786f66d5" + rybbit_site_id = "7e69786f66d5" } diff --git a/modules/kubernetes/finance_app/main.tf b/modules/kubernetes/finance_app/main.tf index b2539e4d..9f21f0ec 100644 --- a/modules/kubernetes/finance_app/main.tf +++ b/modules/kubernetes/finance_app/main.tf @@ -252,16 +252,13 @@ resource "kubernetes_ingress_v1" "finance_app" { name = "finance-app" namespace = kubernetes_namespace.finance_app.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - #"nginx.ingress.kubernetes.io/auth-url"= "https://oauth-provider/auth" - #"nginx.ingress.kubernetes.io/auth-signin"= "https://oauth-provider/sign_in?rd=$request_uri" - "nginx.ingress.kubernetes.io/proxy-connect-timeout" = "600" - "nginx.ingress.kubernetes.io/proxy-send-timeout" = "600" - "nginx.ingress.kubernetes.io/proxy-read-timeout" = "600" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["finance.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/forgejo/main.tf b/modules/kubernetes/forgejo/main.tf index b1960ff0..dd460203 100644 --- a/modules/kubernetes/forgejo/main.tf +++ b/modules/kubernetes/forgejo/main.tf @@ -99,7 +99,4 @@ module "ingress" { namespace = kubernetes_namespace.forgejo.metadata[0].name name = "forgejo" tls_secret_name = var.tls_secret_name - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "20000m" - } } diff --git a/modules/kubernetes/frigate/main.tf b/modules/kubernetes/frigate/main.tf index 32322951..1d8b962a 100644 --- a/modules/kubernetes/frigate/main.tf +++ b/modules/kubernetes/frigate/main.tf @@ -196,20 +196,7 @@ module "ingress" { name = "frigate" tls_secret_name = var.tls_secret_name protected = true - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "20000m" - # Websockets - "nginx.org/websocket-services" : "frigate" - "nginx.ingress.kubernetes.io/proxy-set-header" : "Upgrade $http_upgrade" - "nginx.ingress.kubernetes.io/proxy-set-header" : "Connection $connection_upgrade" - "nginx.ingress.kubernetes.io/proxy-redirect-from" : "off" - - "nginx.ingress.kubernetes.io/limit-rps" : 50000 - "nginx.ingress.kubernetes.io/limit-rpm" : 1000000 - "nginx.ingress.kubernetes.io/limit-burst-multiplier" : 50000 - "nginx.ingress.kubernetes.io/limit-rate-after" : 100000 - } - rybbit_site_id = "0d4044069ff5" + rybbit_site_id = "0d4044069ff5" } module "ingress-internal" { @@ -222,17 +209,4 @@ module "ingress-internal" { tls_secret_name = var.tls_secret_name allow_local_access_only = true ssl_redirect = false - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "20000m" - # Websockets - "nginx.org/websocket-services" : "frigate" - "nginx.ingress.kubernetes.io/proxy-set-header" : "Upgrade $http_upgrade" - "nginx.ingress.kubernetes.io/proxy-set-header" : "Connection $connection_upgrade" - "nginx.ingress.kubernetes.io/proxy-redirect-from" : "off" - - "nginx.ingress.kubernetes.io/limit-rps" : 50000 - "nginx.ingress.kubernetes.io/limit-rpm" : 1000000 - "nginx.ingress.kubernetes.io/limit-burst-multiplier" : 50000 - "nginx.ingress.kubernetes.io/limit-rate-after" : 100000 - } } diff --git a/modules/kubernetes/hackmd/main.tf b/modules/kubernetes/hackmd/main.tf index e8bbdaed..1e21b003 100644 --- a/modules/kubernetes/hackmd/main.tf +++ b/modules/kubernetes/hackmd/main.tf @@ -150,7 +150,4 @@ module "ingress" { namespace = kubernetes_namespace.hackmd.metadata[0].name name = "hackmd" tls_secret_name = var.tls_secret_name - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "20000m" - } } diff --git a/modules/kubernetes/home_assistant/main.tf b/modules/kubernetes/home_assistant/main.tf index 38a9119b..8d025efa 100644 --- a/modules/kubernetes/home_assistant/main.tf +++ b/modules/kubernetes/home_assistant/main.tf @@ -206,14 +206,14 @@ resource "kubernetes_ingress_v1" "home-assistant-ui" { name = "home-assistant-ui-ingress" namespace = kubernetes_namespace.home_assistant.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/force-ssl-redirect" = "true" - "nginx.ingress.kubernetes.io/auth-tls-verify-client" = "on" - "nginx.ingress.kubernetes.io/auth-tls-secret" = var.client_certificate_secret_name + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" + "traefik.ingress.kubernetes.io/router.tls.options" = "traefik-mtls@kubernetescrd" } } spec { + ingress_class_name = "traefik" tls { hosts = ["home-assistant.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/homepage/values.yaml b/modules/kubernetes/homepage/values.yaml index 00234f49..121ee4ea 100644 --- a/modules/kubernetes/homepage/values.yaml +++ b/modules/kubernetes/homepage/values.yaml @@ -45,7 +45,7 @@ ingress: gethomepage.dev/description: "A modern, secure, highly customizable application dashboard." gethomepage.dev/group: "A New Group" gethomepage.dev/icon: "homepage.png" - ingressClassName: "nginx" + ingressClassName: "traefik" hosts: - host: &host "home.viktorbarzin.me" paths: diff --git a/modules/kubernetes/immich/main.tf b/modules/kubernetes/immich/main.tf index 3a833362..cd785535 100644 --- a/modules/kubernetes/immich/main.tf +++ b/modules/kubernetes/immich/main.tf @@ -460,66 +460,8 @@ resource "kubernetes_ingress_v1" "ingress" { namespace = kubernetes_namespace.immich.metadata[0].name name = "immich" annotations = { - # NOTE: when changing - test video playback from mobile and web! - # Easy to break! - - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/backend-protocol" = "HTTP" - - # As per https://immich.app/docs/administration/reverse-proxy - "nginx.org/websocket-services" : "immich-server" - # Websockets - "nginx.ingress.kubernetes.io/proxy-set-header" : "Upgrade $http_upgrade" - "nginx.ingress.kubernetes.io/proxy-set-header" : "Connection $connection_upgrade" # this makes a difference for web!!! - "nginx.ingress.kubernetes.io/proxy-redirect-from" : "off" - # Timeouts - "nginx.ingress.kubernetes.io/proxy-read-timeout" : "6000s", - "nginx.ingress.kubernetes.io/proxy-send-timeout" : "6000s", - - "nginx.ingress.kubernetes.io/proxy-connect-timeout" : "60s" - - # Allow big uploads - "nginx.ingress.kubernetes.io/proxy-body-size" : "0" - "nginx.ingress.kubernetes.io/proxy-buffering" : "off" - "nginx.ingress.kubernetes.io/proxy-request-buffering" : "off" - "nginx.ingress.kubernetes.io/proxy-http-version" : "1.1" - # "nginx.ingress.kubernetes.io/client-body-buffer-size" : "512m" - # "nginx.ingress.kubernetes.io/proxy-buffers-number" : "4" - - # More lenient DDOS protection as to not confuse with image loading - "nginx.ingress.kubernetes.io/limit-connections" : 5000 - "nginx.ingress.kubernetes.io/limit-rps" : 100 - "nginx.ingress.kubernetes.io/limit-rpm" : 6000 - "nginx.ingress.kubernetes.io/limit-burst-multiplier" : 10 - - # good for downloading big files - https://www.pdxdev.com/nginx-content-delivery/configuring-nginx-for-large-file-transfers/ - "nginx.ingress.kubernetes.io/configuration-snippet" : < - sub_filter '' ' - - '; - EOF - - "nginx.ingress.kubernetes.io/enable-modsecurity" : "false" # this is important!!!; setting it to true enables buffering and can lead to ooms when ploading big files - "nginx.ingress.kubernetes.io/enable-owasp-modsecurity-crs" : "false" - + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-immich-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,immich-rybbit-analytics@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" "gethomepage.dev/enabled" = "true" "gethomepage.dev/description" = "Photos library" @@ -533,6 +475,7 @@ resource "kubernetes_ingress_v1" "ingress" { } spec { + ingress_class_name = "traefik" tls { hosts = ["immich.viktorbarzin.me"] secret_name = var.tls_secret_name @@ -724,3 +667,25 @@ resource "kubernetes_cron_job_v1" "postgresql-backup" { # protected = true # } +# Rybbit analytics middleware for Immich +resource "kubernetes_manifest" "rybbit_analytics" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "rybbit-analytics" + namespace = kubernetes_namespace.immich.metadata[0].name + } + spec = { + plugin = { + rewritebody = { + rewrites = [{ + regex = "" + replacement = "" + }] + } + } + } + } +} + diff --git a/modules/kubernetes/jellyfin/main.tf b/modules/kubernetes/jellyfin/main.tf index eb7decb7..eb1dfb74 100644 --- a/modules/kubernetes/jellyfin/main.tf +++ b/modules/kubernetes/jellyfin/main.tf @@ -113,12 +113,13 @@ resource "kubernetes_ingress_v1" "jellyfin" { name = "jellyfin" namespace = kubernetes_namespace.jellyfin.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/proxy-body-size" : "5000m" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["jellyfin.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/kafka/main.tf b/modules/kubernetes/kafka/main.tf index 51e15d5c..a9cd605f 100644 --- a/modules/kubernetes/kafka/main.tf +++ b/modules/kubernetes/kafka/main.tf @@ -110,14 +110,14 @@ resource "kubernetes_ingress_v1" "kafka-ui" { name = "kafka-ui-ingress" namespace = kubernetes_namespace.kafka.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/force-ssl-redirect" = "true" - "nginx.ingress.kubernetes.io/auth-tls-verify-client" = "on" - "nginx.ingress.kubernetes.io/auth-tls-secret" = var.client_certificate_secret_name + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" + "traefik.ingress.kubernetes.io/router.tls.options" = "traefik-mtls@kubernetescrd" } } spec { + ingress_class_name = "traefik" tls { hosts = ["kafka.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/monitoring/grafana_chart_values.yaml b/modules/kubernetes/monitoring/grafana_chart_values.yaml index 1560bb12..9a32eece 100644 --- a/modules/kubernetes/monitoring/grafana_chart_values.yaml +++ b/modules/kubernetes/monitoring/grafana_chart_values.yaml @@ -6,14 +6,10 @@ persistence: existingClaim: "grafana-pvc" ingress: enabled: "true" + ingressClassName: "traefik" annotations: - kubernetes.io/ingress.class: nginx - # nginx.ingress.kubernetes.io/auth-url: "https://oauth2.viktorbarzin.me/oauth2/auth" - # nginx.ingress.kubernetes.io/auth-signin: "https://oauth2.viktorbarzin.me/oauth2/start?rd=/redirect/$http_host$escaped_request_uri" - nginx.ingress.kubernetes.io/auth-url: "http://ak-outpost-authentik-embedded-outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/nginx" - nginx.ingress.kubernetes.io/auth-signin: "https://authentik.viktorbarzin.me/outpost.goauthentik.io/start?rd=$scheme%3A%2F%2F$host$escaped_request_uri" - nginx.ingress.kubernetes.io/auth-response-headers: "Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid" - nginx.ingress.kubernetes.io/auth-snippet: "proxy_set_header X-Forwarded-Host $http_host;" + traefik.ingress.kubernetes.io/router.middlewares: "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,traefik-authentik-forward-auth@kubernetescrd" + traefik.ingress.kubernetes.io/router.entrypoints: "websecure" tls: - secretName: "tls-secret" hosts: diff --git a/modules/kubernetes/monitoring/main.tf b/modules/kubernetes/monitoring/main.tf index 12fadc03..6cf08256 100644 --- a/modules/kubernetes/monitoring/main.tf +++ b/modules/kubernetes/monitoring/main.tf @@ -75,17 +75,36 @@ resource "kubernetes_cron_job_v1" "monitor_prom" { } } +resource "kubernetes_manifest" "status_redirect_middleware" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "status-redirect" + namespace = kubernetes_namespace.monitoring.metadata[0].name + } + spec = { + redirectRegex = { + regex = ".*" + replacement = "https://hetrixtools.com/r/38981b548b5d38b052aca8d01285a3f3/" + permanent = true + } + } + } +} + resource "kubernetes_ingress_v1" "status" { metadata { name = "hetrix-redirect-ingress" namespace = kubernetes_namespace.monitoring.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/permanent-redirect" = "https://hetrixtools.com/r/38981b548b5d38b052aca8d01285a3f3/" + "traefik.ingress.kubernetes.io/router.middlewares" = "monitoring-status-redirect@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["status.viktorbarzin.me"] secret_name = var.tls_secret_name @@ -99,7 +118,7 @@ resource "kubernetes_ingress_v1" "status" { service { name = "not-used" port { - number = 80 # redirected by annotation + number = 80 # redirected by middleware } } } @@ -109,17 +128,36 @@ resource "kubernetes_ingress_v1" "status" { } } +resource "kubernetes_manifest" "yotovski_redirect_middleware" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "yotovski-redirect" + namespace = kubernetes_namespace.monitoring.metadata[0].name + } + spec = { + redirectRegex = { + regex = ".*" + replacement = "https://hetrixtools.com/r/2ba9d7a5e017794db0fd91f0115a8b3b/" + permanent = true + } + } + } +} + resource "kubernetes_ingress_v1" "status_yotovski" { metadata { name = "hetrix-yotovski-redirect-ingress" namespace = kubernetes_namespace.monitoring.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/permanent-redirect" = "https://hetrixtools.com/r/2ba9d7a5e017794db0fd91f0115a8b3b/" + "traefik.ingress.kubernetes.io/router.middlewares" = "monitoring-yotovski-redirect@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["yotovski-status.viktorbarzin.me"] secret_name = var.tls_secret_name @@ -131,7 +169,7 @@ resource "kubernetes_ingress_v1" "status_yotovski" { path = "/" backend { service { - name = "not-used" # redirected by annotation + name = "not-used" # redirected by middleware port { number = 80 } diff --git a/modules/kubernetes/monitoring/prometheus_chart_values.tpl b/modules/kubernetes/monitoring/prometheus_chart_values.tpl index c65e9358..c869dc3e 100755 --- a/modules/kubernetes/monitoring/prometheus_chart_values.tpl +++ b/modules/kubernetes/monitoring/prometheus_chart_values.tpl @@ -11,19 +11,10 @@ alertmanager: baseURL: "https://alertmanager.viktorbarzin.me" ingress: enabled: true + ingressClassName: "traefik" annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - # Enable client certificate authentication - # nginx.ingress.kubernetes.io/auth-tls-verify-client: "on" - # Create the secret containing the trusted ca certificates - # nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret" - # nginx.ingress.kubernetes.io/auth-url: "https://oauth2.viktorbarzin.me/oauth2/auth" - # nginx.ingress.kubernetes.io/auth-signin: "https://oauth2.viktorbarzin.me/oauth2/start?rd=/redirect/$http_host$escaped_request_uri" - nginx.ingress.kubernetes.io/auth-url: "http://ak-outpost-authentik-embedded-outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/nginx" - nginx.ingress.kubernetes.io/auth-signin: "https://authentik.viktorbarzin.me/outpost.goauthentik.io/start?rd=$scheme%3A%2F%2F$host$escaped_request_uri" - nginx.ingress.kubernetes.io/auth-response-headers: "Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid" - nginx.ingress.kubernetes.io/auth-snippet: "proxy_set_header X-Forwarded-Host $http_host;" + traefik.ingress.kubernetes.io/router.middlewares: "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,traefik-authentik-forward-auth@kubernetescrd" + traefik.ingress.kubernetes.io/router.entrypoints: "websecure" tls: - secretName: "tls-secret" hosts: @@ -100,19 +91,10 @@ server: mountPath: /data/wal # Standard path for the chart ingress: enabled: true + ingressClassName: "traefik" annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - # Enable client certificate authentication - # nginx.ingress.kubernetes.io/auth-tls-verify-client: "on" - # Create the secret containing the trusted ca certificates - # nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret" - # nginx.ingress.kubernetes.io/auth-url: "https://oauth2.viktorbarzin.me/oauth2/auth" - # nginx.ingress.kubernetes.io/auth-signin: "https://oauth2.viktorbarzin.me/oauth2/start?rd=/redirect/$http_host$escaped_request_uri" - "nginx.ingress.kubernetes.io/auth-url": "http://ak-outpost-authentik-embedded-outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/nginx" - "nginx.ingress.kubernetes.io/auth-signin": "https://authentik.viktorbarzin.me/outpost.goauthentik.io/start?rd=$scheme%3A%2F%2F$host$escaped_request_uri" - "nginx.ingress.kubernetes.io/auth-response-headers": "Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid" - "nginx.ingress.kubernetes.io/auth-snippet": "proxy_set_header X-Forwarded-Host $http_host;" + traefik.ingress.kubernetes.io/router.middlewares: "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,traefik-authentik-forward-auth@kubernetescrd" + traefik.ingress.kubernetes.io/router.entrypoints: "websecure" gethomepage.dev/enabled: "true" gethomepage.dev/description: "Prometheus" diff --git a/modules/kubernetes/n8n/main.tf b/modules/kubernetes/n8n/main.tf index 77c06fe4..6a7b59a8 100644 --- a/modules/kubernetes/n8n/main.tf +++ b/modules/kubernetes/n8n/main.tf @@ -134,7 +134,4 @@ module "ingress" { namespace = kubernetes_namespace.n8n.metadata[0].name name = "n8n" tls_secret_name = var.tls_secret_name - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "20000m" - } } diff --git a/modules/kubernetes/nextcloud/main.tf b/modules/kubernetes/nextcloud/main.tf index fc8c854b..a35828e3 100755 --- a/modules/kubernetes/nextcloud/main.tf +++ b/modules/kubernetes/nextcloud/main.tf @@ -154,13 +154,7 @@ module "ingress" { name = "nextcloud" tls_secret_name = var.tls_secret_name port = 8080 - extra_annotations = { - "nginx.ingress.kubernetes.io/client-max-body-size" : "0" - "nginx.ingress.kubernetes.io/proxy-body-size" : "0", - "nginx.ingress.kubernetes.io/limit-rps" : 1000 # Increased to allow webdav syncing - "nginx.ingress.kubernetes.io/limit-rpm" : 60000 - } - rybbit_site_id = "5a3bfe59a3fe" + rybbit_site_id = "5a3bfe59a3fe" } module "whiteboard_ingress" { @@ -169,18 +163,6 @@ module "whiteboard_ingress" { name = "whiteboard" tls_secret_name = var.tls_secret_name port = 80 - extra_annotations = { - "nginx.ingress.kubernetes.io/client-max-body-size" : "0" - "nginx.ingress.kubernetes.io/proxy-body-size" : "0", - - # Websockets - # "nginx.ingress.kubernetes.io/proxy-set-header" : "Upgrade $http_upgrade" - # "nginx.ingress.kubernetes.io/proxy-set-header" : "Connection $connection_upgrade" # this makes a difference for web!!! - - # Timeouts - "nginx.ingress.kubernetes.io/proxy-read-timeout" : "6000s", - "nginx.ingress.kubernetes.io/proxy-send-timeout" : "6000s", - } } resource "kubernetes_config_map" "backup-script" { diff --git a/modules/kubernetes/nginx-ingress/main.tf b/modules/kubernetes/nginx-ingress/main.tf deleted file mode 100644 index ab511f7a..00000000 --- a/modules/kubernetes/nginx-ingress/main.tf +++ /dev/null @@ -1,910 +0,0 @@ -# module "nginx-controller" { -# source = "terraform-iaac/nginx-controller/helm" -# namespace = "ingress-nginx-test" -# create_namespace = true -# atomic = true -# ingress_class_is_default = false -# ingress_class_name = "nginx-test" -# } -variable "honeypotapikey" { - default = null -} -variable "crowdsec_api_key" {} -variable "crowdsec_captcha_secret_key" {} -variable "crowdsec_captcha_site_key" {} -variable "tier" { type = string } - -resource "kubernetes_namespace" "ingress_nginx" { - metadata { - name = "ingress-nginx" - labels = { - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - # "istio-injection" : "enabled" - } - } -} -resource "kubernetes_service_account" "ingress_nginx" { - metadata { - name = "ingress-nginx" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - automount_service_account_token = true -} - -# Jobs create a cert and modify this secret. This is problematic as TF recreates it every time -# Instead, on each fresh install, uncomment this, get nginx working and comment it. -# Also rm from state: tf state rm module.kubernetes_cluster.module.nginx-ingress.kubernetes_service_account.ingress_nginx_admission -resource "kubernetes_service_account" "ingress_nginx_admission" { - metadata { - name = "ingress-nginx-admission" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "admission-webhook" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } -} -resource "kubernetes_role" "ingress_nginx" { - metadata { - name = "ingress-nginx" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - rule { - verbs = ["get"] - api_groups = [""] - resources = ["namespaces"] - } - rule { - verbs = ["get", "list", "watch", "update", "create", "delete"] - api_groups = [""] - resources = ["configmaps", "pods", "secrets", "endpoints"] - } - rule { - verbs = ["get", "list", "watch"] - api_groups = [""] - resources = ["services"] - } - rule { - verbs = ["get", "list", "watch"] - api_groups = ["networking.k8s.io"] - resources = ["ingresses"] - } - rule { - verbs = ["update"] - api_groups = ["networking.k8s.io"] - resources = ["ingresses/status"] - } - rule { - verbs = ["get", "list", "watch"] - api_groups = ["networking.k8s.io"] - resources = ["ingressclasses"] - } - rule { - verbs = ["get", "update"] - api_groups = ["coordination.k8s.io"] - resources = ["leases"] - resource_names = ["ingress-nginx-leader"] - } - rule { - verbs = ["create"] - api_groups = ["coordination.k8s.io"] - resources = ["leases"] - } - rule { - verbs = ["create", "patch"] - api_groups = [""] - resources = ["events"] - } - rule { - verbs = ["list", "watch", "get"] - api_groups = ["discovery.k8s.io"] - resources = ["endpointslices"] - } -} -resource "kubernetes_role" "ingress_nginx_admission" { - metadata { - name = "ingress-nginx-admission" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "admission-webhook" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - rule { - verbs = ["get", "create", "patch", "update", "watch", "list"] - api_groups = [""] - resources = ["secrets"] - } -} -resource "kubernetes_cluster_role" "ingress_nginx" { - metadata { - name = "ingress-nginx" - labels = { - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - rule { - verbs = ["list", "watch"] - api_groups = [""] - resources = ["configmaps", "endpoints", "nodes", "pods", "secrets", "namespaces"] - } - rule { - verbs = ["list", "watch"] - api_groups = ["coordination.k8s.io"] - resources = ["leases"] - } - rule { - verbs = ["get"] - api_groups = [""] - resources = ["nodes"] - } - rule { - verbs = ["get", "list", "watch"] - api_groups = [""] - resources = ["services"] - } - rule { - verbs = ["get", "list", "watch"] - api_groups = ["networking.k8s.io"] - resources = ["ingresses"] - } - rule { - verbs = ["create", "patch"] - api_groups = [""] - resources = ["events"] - } - rule { - verbs = ["update"] - api_groups = ["networking.k8s.io"] - resources = ["ingresses/status"] - } - rule { - verbs = ["get", "list", "watch"] - api_groups = ["networking.k8s.io"] - resources = ["ingressclasses"] - } - rule { - verbs = ["list", "watch", "get"] - api_groups = ["discovery.k8s.io"] - resources = ["endpointslices"] - } -} -resource "kubernetes_cluster_role" "ingress_nginx_admission" { - metadata { - name = "ingress-nginx-admission" - labels = { - "app.kubernetes.io/component" = "admission-webhook" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - rule { - verbs = ["get", "update"] - api_groups = ["admissionregistration.k8s.io"] - resources = ["validatingwebhookconfigurations"] - } -} -resource "kubernetes_role_binding" "ingress_nginx" { - metadata { - name = "ingress-nginx" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - subject { - kind = "ServiceAccount" - name = "ingress-nginx" - namespace = "ingress-nginx" - } - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "Role" - name = "ingress-nginx" - } -} -resource "kubernetes_role_binding" "ingress_nginx_admission" { - metadata { - name = "ingress-nginx-admission" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "admission-webhook" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - subject { - kind = "ServiceAccount" - name = "ingress-nginx-admission" - namespace = "ingress-nginx" - } - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "Role" - name = "ingress-nginx-admission" - } -} -resource "kubernetes_cluster_role_binding" "ingress_nginx" { - metadata { - name = "ingress-nginx" - labels = { - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - subject { - kind = "ServiceAccount" - name = "ingress-nginx" - namespace = "ingress-nginx" - } - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "ClusterRole" - name = "ingress-nginx" - } -} -resource "kubernetes_cluster_role_binding" "ingress_nginx_admission" { - metadata { - name = "ingress-nginx-admission" - labels = { - "app.kubernetes.io/component" = "admission-webhook" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - subject { - kind = "ServiceAccount" - name = "ingress-nginx-admission" - namespace = "ingress-nginx" - } - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "ClusterRole" - name = "ingress-nginx-admission" - } -} -resource "kubernetes_config_map" "ingress_nginx_controller" { - metadata { - name = "ingress-nginx-controller" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - data = { - use-forwarded-headers = "true" - compute-full-forwarded-for = "true" - enable-real-ip = "true" - allow-snippet-annotations = true - limit-req-status-code = 429 - limit-conn-status-code = 429 - enable-modsecurity = true - enable-owasp-modsecurity-crs = false - modsecurity-snippet : <<-EOT - SecRuleEngine On - ${var.honeypotapikey != null ? format("%s %s", "SecHttpBlKey", var.honeypotapikey) : ""} - SecAction "id:900500,\ - phase:1,\ - nolog,\ - pass,\ - t:none,\ - setvar:tx.block_search_ip=0,\ - setvar:tx.block_suspicious_ip=1,\ - setvar:tx.block_harvester_ip=1,\ - setvar:tx.block_spammer_ip=1" - EOT - plugins = "crowdsec" - # plugins = "" - lua-shared-dicts = "crowdsec_cache: 50m" - http-snippet : <<-EOT - proxy_cache_path /tmp/nginx-cache levels=1:2 keys_zone=static-cache:2m max_size=100m inactive=7d use_temp_path=off; - proxy_cache_key $scheme$proxy_host$request_uri; - proxy_cache_lock on; - proxy_cache_use_stale updating; - EOT - server-snippet : <<-EOT - lua_ssl_trusted_certificate "/etc/ssl/certs/ca-certificates.crt"; # Captcha - #resolver local=on ipv6=off valid=600s; - EOT - # first own works - # log-format-upstream : <<-EOT - # $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_cf_connecting_ip" "$http_cf_ray" "$http_x_forwarded_for" "$host"; - # EOT - - # ketpt do debug why it's invalid syntax lol - # log-format-upstream : <<-EOT - # $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_cf_connecting_ip" "$http_cf_ray" "$http_x_forwarded_for" "$host"; - # EOT - } -} - -resource "kubernetes_config_map" "udp_services" { - metadata { - name = "udp-services" - namespace = "ingress-nginx" - } - data = { - 53 : "technitium/technitium-dns:53" - # 8554 : "frigate/frigate:8554" - } -} -resource "kubernetes_config_map" "tcp_services" { - metadata { - name = "tcp-services" - namespace = "ingress-nginx" - } - data = { - # 9443 : "wireguard/xray:7443" // reality - # 8554 : "frigate/frigate:8554" - } -} -resource "kubernetes_service" "ingress_nginx_controller" { - metadata { - name = "ingress-nginx-controller" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - spec { - port { - name = "http" - protocol = "TCP" - port = 80 - target_port = "http" - } - port { - name = "https" - protocol = "TCP" - port = 443 - target_port = "https" - } - port { - name = "dns" - protocol = "UDP" - port = 53 - target_port = "dns" - } - # port { - # name = "frigate-rtsptcp" - # port = 8554 - # protocol = "TCP" - # } - # port { - # name = "frigate-rtspudp" - # port = 8554 - # protocol = "UDP" - # } - # port { - # name = "xray-reality" - # protocol = "TCP" - # port = 9443 # expose tcp port here - # target_port = "9443" - # } - selector = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - } - type = "LoadBalancer" - external_traffic_policy = "Local" // see https://metallb.universe.tf/usage/ - # ip_families = ["IPv4"] - } -} -resource "kubernetes_service" "ingress_nginx_controller_admission" { - metadata { - name = "ingress-nginx-controller-admission" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - spec { - port { - name = "https-webhook" - port = 443 - target_port = "webhook" - } - selector = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - } - type = "ClusterIP" - } -} -resource "kubernetes_deployment" "ingress_nginx_controller" { - metadata { - name = "ingress-nginx-controller" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - tier = var.tier - } - annotations = { - "reloader.stakater.com/search" = "true" - } - } - spec { - replicas = 3 - - selector { - match_labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - } - } - template { - metadata { - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - "app" = "ingress-nginx" - } - annotations = { - "prometheus.io/scrape" = "true" - "prometheus.io/port" = 10254 - - "diun.enable" = "true" - "diun.include_tags" = "^v\\d+(?:\\.\\d+)?(?:\\.\\d+)?.*$" - } - } - spec { - volume { - name = "webhook-cert" - secret { - secret_name = "ingress-nginx-admission" - } - } - # volume { - # name = "modsecurity" - # config_map { - # name = "modsecurity" - # } - # } - - ## Crowdsec - init_container { - name = "init-clone-crowdsec-bouncer" - image = "crowdsecurity/lua-bouncer-plugin" - env { - name = "API_URL" - value = "http://crowdsec-service.crowdsec.svc.cluster.local:8080" - } - env { - // if you can't connect with bouncer not found, regenerate api key with: - // "cscli bouncers add nginx" on the lapi - name = "API_KEY" - value = var.crowdsec_api_key - } - env { - name = "MODE" - value = "stream" - } - env { - name = "CAPTCHA_PROVIDER" - value = "hcaptcha" - } - env { - name = "BOUNCING_ON_TYPE" - value = "all" - # value = "ban" - } - env { - name = "SECRET_KEY" - value = var.crowdsec_captcha_secret_key - } - env { - name = "SITE_KEY" - value = var.crowdsec_captcha_site_key - } - - # env { - # name = "DISABLE_RUN" - # value = "true" - # } - env { - name = "BAN_TEMPLATE_PATH" - value = "/etc/nginx/lua/plugins/crowdsec/templates/ban.html" - } - env { - name = "CAPTCHA_TEMPLATE_PATH" - value = "/etc/nginx/lua/plugins/crowdsec/templates/captcha.html" - } - env { - name = "BOUNCER_CONFIG" - value = "/crowdsec/crowdsec-bouncer.conf" - } - # command = ["sh", "-c", "sh /docker_start.sh; mkdir -p /lua_plugins/crowdsec/; cp -r /crowdsec /lua_plugins/; chown -R 101:101 /lua_plugins/"] - command = ["sh", "-c", "sh /docker_start.sh; mkdir -p /lua_plugins/crowdsec/; cp -R /crowdsec/* /lua_plugins/crowdsec/"] - - volume_mount { - name = "crowdsec" - mount_path = "/lua_plugins" - } - } - # Share bouncer config - volume { - name = "crowdsec" - empty_dir { - } - } - container { - name = "controller" - # https://github.com/kubernetes/ingress-nginx - image = "registry.k8s.io/ingress-nginx/controller:v1.11.8" - args = ["/nginx-ingress-controller", "--election-id=ingress-nginx-leader", "--controller-class=k8s.io/ingress-nginx", "--ingress-class=nginx", "--configmap=$(POD_NAMESPACE)/ingress-nginx-controller", "--validating-webhook=:8443", "--validating-webhook-certificate=/usr/local/certificates/cert", "--validating-webhook-key=/usr/local/certificates/key", "--udp-services-configmap", "ingress-nginx/udp-services", "--tcp-services-configmap", "ingress-nginx/tcp-services"] - volume_mount { - name = "crowdsec" - mount_path = "/etc/nginx/lua/plugins/crowdsec" - sub_path = "crowdsec" - } - port { - name = "http" - container_port = 80 - protocol = "TCP" - } - port { - name = "https" - container_port = 443 - protocol = "TCP" - } - port { - name = "dns" - container_port = 53 - protocol = "UDP" - } - # port { - # name = "xray-reality" - # container_port = 9443 # expose port here - # protocol = "TCP" - # } - port { - name = "webhook" - container_port = 8443 - protocol = "TCP" - } - # port { - # name = "frigate-rtsptcp" - # container_port = 8554 - # protocol = "TCP" - # } - # port { - # name = "frigate-rtspudp" - # container_port = 8554 - # protocol = "UDP" - # } - port { - name = "metrics" - container_port = 10254 - protocol = "TCP" - } - env { - name = "POD_NAME" - value_from { - field_ref { - field_path = "metadata.name" - } - } - } - env { - name = "POD_NAMESPACE" - value_from { - field_ref { - field_path = "metadata.namespace" - } - } - } - env { - name = "LD_PRELOAD" - value = "/usr/local/lib/libmimalloc.so" - } - resources { - requests = { - cpu = "100m" - memory = "90Mi" - } - } - volume_mount { - name = "webhook-cert" - read_only = true - mount_path = "/usr/local/certificates/" - } - # Not used atm - # volume_mount { - # name = "modsecurity" - # read_only = true - # mount_path = "/etc/nginx/modsecurity" - # # sub_path = "modsecurity.conf" - # } - liveness_probe { - http_get { - path = "/healthz" - port = "10254" - scheme = "HTTP" - } - initial_delay_seconds = 10 - timeout_seconds = 1 - period_seconds = 10 - success_threshold = 1 - failure_threshold = 5 - } - readiness_probe { - http_get { - path = "/healthz" - port = "10254" - scheme = "HTTP" - } - initial_delay_seconds = 10 - timeout_seconds = 1 - period_seconds = 10 - success_threshold = 1 - failure_threshold = 3 - } - lifecycle { - pre_stop { - exec { - command = ["/wait-shutdown"] - } - } - } - image_pull_policy = "IfNotPresent" - security_context { - capabilities { - add = ["NET_BIND_SERVICE"] - drop = ["ALL"] - } - run_as_user = 101 - allow_privilege_escalation = true - } - } - termination_grace_period_seconds = 300 - dns_policy = "ClusterFirst" - node_selector = { - "kubernetes.io/os" = "linux" - } - service_account_name = "ingress-nginx" - } - } - strategy { - type = "RollingUpdate" - rolling_update { - max_unavailable = "1" - max_surge = "2" - } - } - - revision_history_limit = 10 - } -} -resource "kubernetes_job" "ingress_nginx_admission_create" { - metadata { - name = "ingress-nginx-admission-create" - namespace = "ingress-nginx" - labels = { - "app.kubernetes.io/component" = "admission-webhook" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.8.2" - } - } - spec { - template { - metadata { - name = "ingress-nginx-admission-create" - labels = { - "app.kubernetes.io/component" = "admission-webhook" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.8.2" - } - } - spec { - container { - name = "create" - image = "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b" - args = ["create", "--host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc", "--namespace=$(POD_NAMESPACE)", "--secret-name=ingress-nginx-admission"] - env { - name = "POD_NAMESPACE" - value_from { - field_ref { - field_path = "metadata.namespace" - } - } - } - image_pull_policy = "IfNotPresent" - } - restart_policy = "OnFailure" - node_selector = { - "kubernetes.io/os" = "linux" - } - service_account_name = "ingress-nginx-admission" - security_context { - run_as_user = 2000 - run_as_non_root = true - fs_group = 2000 - } - } - } - } -} - -# Jobs create a cert and modify this secret. This is problematic as TF recreates it every time -# Instead, on each fresh install, uncomment this, get nginx working and comment it. -# Also rm from state: tf state rm module.kubernetes_cluster.module.nginx-ingress.kubernetes_job.ingress_nginx_admission_patch -# resource "kubernetes_job" "ingress_nginx_admission_patch" { -# metadata { -# name = "ingress-nginx-admission-patch" -# namespace = "ingress-nginx" -# labels = { -# "app.kubernetes.io/component" = "admission-webhook" -# "app.kubernetes.io/instance" = "ingress-nginx" -# "app.kubernetes.io/name" = "ingress-nginx" -# "app.kubernetes.io/part-of" = "ingress-nginx" -# "app.kubernetes.io/version" = "1.13.1" -# } -# } -# spec { -# template { -# metadata { -# name = "ingress-nginx-admission-patch" -# labels = { -# "app.kubernetes.io/component" = "admission-webhook" -# "app.kubernetes.io/instance" = "ingress-nginx" -# "app.kubernetes.io/name" = "ingress-nginx" -# "app.kubernetes.io/part-of" = "ingress-nginx" -# "app.kubernetes.io/version" = "1.13.1" -# } -# } -# spec { -# container { -# name = "patch" -# image = "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b" -# args = ["patch", "--webhook-name=ingress-nginx-admission", "--namespace=$(POD_NAMESPACE)", "--patch-mutating=false", "--secret-name=ingress-nginx-admission", "--patch-failure-policy=Fail"] -# env { -# name = "POD_NAMESPACE" -# value_from { -# field_ref { -# field_path = "metadata.namespace" -# } -# } -# } -# image_pull_policy = "IfNotPresent" -# } -# restart_policy = "OnFailure" -# node_selector = { -# "kubernetes.io/os" = "linux" -# } -# service_account_name = "ingress-nginx-admission" -# security_context { -# run_as_user = 2000 -# run_as_non_root = true -# fs_group = 2000 -# } -# } -# } -# } -# } -resource "kubernetes_ingress_class" "nginx" { - metadata { - name = "nginx" - labels = { - "app.kubernetes.io/component" = "controller" - "app.kubernetes.io/instance" = "ingress-nginx" - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - "app.kubernetes.io/version" = "1.13.1" - } - } - spec { - controller = "k8s.io/ingress-nginx" - } -} - -# Jobs create a cert and modify this secret. This is problematic as TF recreates it every time -# Instead, on each fresh install, uncomment this, get nginx working and comment it. -# Also rm from state: tf state rm module.kubernetes_cluster.module.nginx-ingress.kubernetes_service_account.ingress_nginx_admission -# resource "kubernetes_validating_webhook_configuration" "ingress_nginx_admission" { -# metadata { -# name = "ingress-nginx-admission" -# labels = { -# "app.kubernetes.io/component" = "admission-webhook" -# "app.kubernetes.io/instance" = "ingress-nginx" -# "app.kubernetes.io/name" = "ingress-nginx" -# "app.kubernetes.io/part-of" = "ingress-nginx" -# "app.kubernetes.io/version" = "1.13.1" -# } -# } -# webhook { -# name = "validate.nginx.ingress.kubernetes.io" -# client_config { -# service { -# namespace = "ingress-nginx" -# name = "ingress-nginx-controller-admission" -# path = "/networking/v1/ingresses" -# } -# } -# rule { -# api_versions = ["v1"] -# api_groups = ["networking.k8s.io"] -# resources = ["ingresses"] -# operations = ["CREATE", "UPDATE"] -# } -# failure_policy = "Fail" -# match_policy = "Equivalent" -# side_effects = "None" -# admission_review_versions = ["v1"] -# } -# } - -resource "kubernetes_config_map" "modsecurity" { - metadata { - name = "modsecurity" - namespace = "ingress-nginx" - annotations = { - "reloader.stakater.com/match" = "true" - } - } - - data = { - "modsecurity.conf" = file("${path.module}/modsecurity.conf") - } -} diff --git a/modules/kubernetes/nginx-ingress/modsecurity.conf b/modules/kubernetes/nginx-ingress/modsecurity.conf deleted file mode 100644 index a9582678..00000000 --- a/modules/kubernetes/nginx-ingress/modsecurity.conf +++ /dev/null @@ -1,289 +0,0 @@ -### NOT USED ATM - - -# -- Rule engine initialization ---------------------------------------------- - -# Enable ModSecurity, attaching it to every transaction. Use detection -# only to start with, because that minimises the chances of post-installation -# disruption. -# -SecRuleEngine DetectionOnly - - -# -- Request body handling --------------------------------------------------- - -# Allow ModSecurity to access request bodies. If you don't, ModSecurity -# won't be able to see any POST parameters, which opens a large security -# hole for attackers to exploit. -# -SecRequestBodyAccess On - - -# Enable XML request body parser. -# Initiate XML Processor in case of xml content-type -# -SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \ - "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" - -# Enable JSON request body parser. -# Initiate JSON Processor in case of JSON content-type; change accordingly -# if your application does not use 'application/json' -# -SecRule REQUEST_HEADERS:Content-Type "^application/json" \ - "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" - -# Sample rule to enable JSON request body parser for more subtypes. -# Uncomment or adapt this rule if you want to engage the JSON -# Processor for "+json" subtypes -# -#SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \ -# "id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" - -# Maximum request body size we will accept for buffering. If you support -# file uploads then the value given on the first line has to be as large -# as the largest file you are willing to accept. The second value refers -# to the size of data, with files excluded. You want to keep that value as -# low as practical. -# -SecRequestBodyLimit 13107200 -SecRequestBodyNoFilesLimit 131072 - -# What to do if the request body size is above our configured limit. -# Keep in mind that this setting will automatically be set to ProcessPartial -# when SecRuleEngine is set to DetectionOnly mode in order to minimize -# disruptions when initially deploying ModSecurity. -# -SecRequestBodyLimitAction Reject - -# Maximum parsing depth allowed for JSON objects. You want to keep this -# value as low as practical. -# -SecRequestBodyJsonDepthLimit 512 - -# Maximum number of args allowed per request. You want to keep this -# value as low as practical. The value should match that in rule 200007. -SecArgumentsLimit 1000 - -# If SecArgumentsLimit has been set, you probably want to reject any -# request body that has only been partly parsed. The value used in this -# rule should match what was used with SecArgumentsLimit -SecRule &ARGS "@ge 1000" \ -"id:'200007', phase:2,t:none,log,deny,status:400,msg:'Failed to fully parse request body due to large argument count',severity:2" - -# Verify that we've correctly processed the request body. -# As a rule of thumb, when failing to process a request body -# you should reject the request (when deployed in blocking mode) -# or log a high-severity alert (when deployed in detection-only mode). -# -SecRule REQBODY_ERROR "!@eq 0" \ -"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" - -# By default be strict with what we accept in the multipart/form-data -# request body. If the rule below proves to be too strict for your -# environment consider changing it to detection-only. You are encouraged -# _not_ to remove it altogether. -# -SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ -"id:'200003',phase:2,t:none,log,deny,status:400, \ -msg:'Multipart request body failed strict validation: \ -PE %{REQBODY_PROCESSOR_ERROR}, \ -BQ %{MULTIPART_BOUNDARY_QUOTED}, \ -BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ -DB %{MULTIPART_DATA_BEFORE}, \ -DA %{MULTIPART_DATA_AFTER}, \ -HF %{MULTIPART_HEADER_FOLDING}, \ -LF %{MULTIPART_LF_LINE}, \ -SM %{MULTIPART_MISSING_SEMICOLON}, \ -IQ %{MULTIPART_INVALID_QUOTING}, \ -IP %{MULTIPART_INVALID_PART}, \ -IH %{MULTIPART_INVALID_HEADER_FOLDING}, \ -FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" - -# Did we see anything that might be a boundary? -# -# Here is a short description about the ModSecurity Multipart parser: the -# parser returns with value 0, if all "boundary-like" line matches with -# the boundary string which given in MIME header. In any other cases it returns -# with different value, eg. 1 or 2. -# -# The RFC 1341 descript the multipart content-type and its syntax must contains -# only three mandatory lines (above the content): -# * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING -# * --BOUNDARY_STRING -# * --BOUNDARY_STRING-- -# -# First line indicates, that this is a multipart content, second shows that -# here starts a part of the multipart content, third shows the end of content. -# -# If there are any other lines, which starts with "--", then it should be -# another boundary id - or not. -# -# After 3.0.3, there are two kinds of types of boundary errors: strict and permissive. -# -# If multipart content contains the three necessary lines with correct order, but -# there are one or more lines with "--", then parser returns with value 2 (non-zero). -# -# If some of the necessary lines (usually the start or end) misses, or the order -# is wrong, then parser returns with value 1 (also a non-zero). -# -# You can choose, which one is what you need. The example below contains the -# 'strict' mode, which means if there are any lines with start of "--", then -# ModSecurity blocked the content. But the next, commented example contains -# the 'permissive' mode, then you check only if the necessary lines exists in -# correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."), -# or other text files, which contains eg. HTTP headers. -# -# The difference is only the operator - in strict mode (first) the content blocked -# in case of any non-zero value. In permissive mode (second, commented) the -# content blocked only if the value is explicit 1. If it 0 or 2, the content will -# allowed. -# - -# -# See #1747 and #1924 for further information on the possible values for -# MULTIPART_UNMATCHED_BOUNDARY. -# -SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \ - "id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" - - -# PCRE Tuning -# We want to avoid a potential RegEx DoS condition -# -SecPcreMatchLimit 1000 -SecPcreMatchLimitRecursion 1000 - -# Some internal errors will set flags in TX and we will need to look for these. -# All of these are prefixed with "MSC_". The following flags currently exist: -# -# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded. -# -SecRule TX:/^MSC_/ "!@streq 0" \ - "id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'" - - -# -- Response body handling -------------------------------------------------- - -# Allow ModSecurity to access response bodies. -# You should have this directive enabled in order to identify errors -# and data leakage issues. -# -# Do keep in mind that enabling this directive does increases both -# memory consumption and response latency. -# -SecResponseBodyAccess On - -# Which response MIME types do you want to inspect? You should adjust the -# configuration below to catch documents but avoid static files -# (e.g., images and archives). -# -SecResponseBodyMimeType text/plain text/html text/xml - -# Buffer response bodies of up to 512 KB in length. -SecResponseBodyLimit 524288 - -# What happens when we encounter a response body larger than the configured -# limit? By default, we process what we have and let the rest through. -# That's somewhat less secure, but does not break any legitimate pages. -# -SecResponseBodyLimitAction ProcessPartial - - -# -- Filesystem configuration ------------------------------------------------ - -# The location where ModSecurity stores temporary files (for example, when -# it needs to handle a file upload that is larger than the configured limit). -# -# This default setting is chosen due to all systems have /tmp available however, -# this is less than ideal. It is recommended that you specify a location that's private. -# -SecTmpDir /tmp/ - -# The location where ModSecurity will keep its persistent data. This default setting -# is chosen due to all systems have /tmp available however, it -# too should be updated to a place that other users can't access. -# -SecDataDir /tmp/ - - -# -- File uploads handling configuration ------------------------------------- - -# The location where ModSecurity stores intercepted uploaded files. This -# location must be private to ModSecurity. You don't want other users on -# the server to access the files, do you? -# -#SecUploadDir /opt/modsecurity/var/upload/ - -# By default, only keep the files that were determined to be unusual -# in some way (by an external inspection script). For this to work you -# will also need at least one file inspection rule. -# -#SecUploadKeepFiles RelevantOnly - -# Uploaded files are by default created with permissions that do not allow -# any other user to access them. You may need to relax that if you want to -# interface ModSecurity to an external program (e.g., an anti-virus). -# -#SecUploadFileMode 0600 - - -# -- Debug log configuration ------------------------------------------------- - -# The default debug log configuration is to duplicate the error, warning -# and notice messages from the error log. -# -#SecDebugLog /opt/modsecurity/var/log/debug.log -#SecDebugLogLevel 3 - - -# -- Audit log configuration ------------------------------------------------- - -# Log the transactions that are marked by a rule, as well as those that -# trigger a server error (determined by a 5xx or 4xx, excluding 404, -# level response status codes). -# -SecAuditEngine RelevantOnly -SecAuditLogRelevantStatus "^(?:5|4(?!04))" - -# Log everything we know about a transaction. -SecAuditLogParts ABIJDEFHZ - -# Use a single file for logging. This is much easier to look at, but -# assumes that you will use the audit log only ocassionally. -# -SecAuditLogType Concurrent -SecAuditLog /var/log/modsec_audit.log - -# Specify the path for concurrent audit logging. -#SecAuditLogStorageDir /opt/modsecurity/var/audit/ - - -# -- Miscellaneous ----------------------------------------------------------- - -# Use the most commonly used application/x-www-form-urlencoded parameter -# separator. There's probably only one application somewhere that uses -# something else so don't expect to change this value. -# -SecArgumentSeparator & - -# Settle on version 0 (zero) cookies, as that is what most applications -# use. Using an incorrect cookie version may open your installation to -# evasion attacks (against the rules that examine named cookies). -# -SecCookieFormat 0 - -# Specify your Unicode Code Point. -# This mapping is used by the t:urlDecodeUni transformation function -# to properly map encoded data to your language. Properly setting -# these directives helps to reduce false positives and negatives. -# -SecUnicodeMapFile unicode.mapping 20127 - -# Improve the quality of ModSecurity by sharing information about your -# current ModSecurity version and dependencies versions. -# The following information will be shared: ModSecurity version, -# Web Server version, APR version, PCRE version, Lua version, Libxml2 -# version, Anonymous unique id for host. -SecStatusEngine On - -SecAuditLogStorageDir /var/log/audit/ diff --git a/modules/kubernetes/oauth-proxy/main.tf b/modules/kubernetes/oauth-proxy/main.tf index e7a24dcf..f4f5149d 100644 --- a/modules/kubernetes/oauth-proxy/main.tf +++ b/modules/kubernetes/oauth-proxy/main.tf @@ -215,11 +215,13 @@ resource "kubernetes_ingress_v1" "oauth" { name = "oauth2" namespace = "oauth2" annotations = { - "kubernetes.io/ingress.class" = "nginx" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["oauth2.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/ollama/main.tf b/modules/kubernetes/ollama/main.tf index fac512e5..c08b6700 100644 --- a/modules/kubernetes/ollama/main.tf +++ b/modules/kubernetes/ollama/main.tf @@ -169,7 +169,6 @@ module "ollama-api-ingress" { allow_local_access_only = true # Restricts to 10.0.0.0/8, 192.168.1.0/24 ssl_redirect = false port = 11434 - proxy_timeout = 300 # Longer timeout for model inference } # Web UI diff --git a/modules/kubernetes/openid_help_page/main.tf b/modules/kubernetes/openid_help_page/main.tf index 338b92e1..5aa72783 100644 --- a/modules/kubernetes/openid_help_page/main.tf +++ b/modules/kubernetes/openid_help_page/main.tf @@ -83,11 +83,13 @@ resource "kubernetes_ingress_v1" "openid_help_page" { name = "openid-help-page" namespace = "openid-help-page" annotations = { - "kubernetes.io/ingress.class" = "nginx" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["kubectl.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/owntracks/main.tf b/modules/kubernetes/owntracks/main.tf index 9a68196c..ea79dabe 100644 --- a/modules/kubernetes/owntracks/main.tf +++ b/modules/kubernetes/owntracks/main.tf @@ -140,8 +140,22 @@ module "ingress" { tls_secret_name = var.tls_secret_name port = 443 extra_annotations = { - "nginx.ingress.kubernetes.io/auth-type" = "basic" # support only basic auth; can't use authentik - "nginx.ingress.kubernetes.io/auth-secret" = kubernetes_secret.basic_auth.metadata[0].name - "nginx.ingress.kubernetes.io/auth-realm" = "Authentication Required" + "traefik.ingress.kubernetes.io/router.middlewares" = "owntracks-basic-auth@kubernetescrd,traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + } +} + +resource "kubernetes_manifest" "basic_auth_middleware" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "basic-auth" + namespace = kubernetes_namespace.owntracks.metadata[0].name + } + spec = { + basicAuth = { + secret = kubernetes_secret.basic_auth.metadata[0].name + } + } } } diff --git a/modules/kubernetes/paperless-ngx/main.tf b/modules/kubernetes/paperless-ngx/main.tf index e2bcce71..24aa6e9b 100644 --- a/modules/kubernetes/paperless-ngx/main.tf +++ b/modules/kubernetes/paperless-ngx/main.tf @@ -154,10 +154,6 @@ module "ingress" { tls_secret_name = var.tls_secret_name port = 80 extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "0" - # see https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md#rate-limiting for all annotations - # "nginx.ingress.kubernetes.io/limit-rpm": "5" - "gethomepage.dev/enabled" = "true" "gethomepage.dev/description" = "Document library" # gethomepage.dev/group: Media diff --git a/modules/kubernetes/pihole/main.tf b/modules/kubernetes/pihole/main.tf index 1dc39eb6..ede0c04a 100644 --- a/modules/kubernetes/pihole/main.tf +++ b/modules/kubernetes/pihole/main.tf @@ -169,13 +169,14 @@ resource "kubernetes_ingress_v1" "pihole" { name = "pihole-ingress" namespace = kubernetes_namespace.pihole.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/auth-tls-verify-client" = "on" - "nginx.ingress.kubernetes.io/auth-tls-secret" = "default/ca-secret" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" + "traefik.ingress.kubernetes.io/router.tls.options" = "traefik-mtls@kubernetescrd" } } spec { + ingress_class_name = "traefik" tls { hosts = ["pihole.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/plotting-book/main.tf b/modules/kubernetes/plotting-book/main.tf index 692737ee..c67e5fd0 100644 --- a/modules/kubernetes/plotting-book/main.tf +++ b/modules/kubernetes/plotting-book/main.tf @@ -89,9 +89,5 @@ module "ingress" { name = "plotting-book" tls_secret_name = var.tls_secret_name - additional_configuration_snippet = <<-EOF - # Override CSP to allow data: URIs and blob: for database/workers - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "default-src 'self' blob: data:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; worker-src 'self' blob:; connect-src 'self' blob:; frame-ancestors 'self' *.viktorbarzin.me viktorbarzin.me" always; - EOF + custom_content_security_policy = "default-src 'self' blob: data:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; worker-src 'self' blob:; connect-src 'self' blob:; frame-ancestors 'self' *.viktorbarzin.me viktorbarzin.me" } diff --git a/modules/kubernetes/privatebin/main.tf b/modules/kubernetes/privatebin/main.tf index 199fe729..0c5a2f4a 100644 --- a/modules/kubernetes/privatebin/main.tf +++ b/modules/kubernetes/privatebin/main.tf @@ -89,13 +89,11 @@ resource "kubernetes_service" "privatebin" { } module "ingress" { - source = "../ingress_factory" - namespace = kubernetes_namespace.privatebin.metadata[0].name - name = "privatebin" - host = "pb" - tls_secret_name = var.tls_secret_name - rybbit_site_id = "3ae810b0476d" - additional_configuration_snippet = <<-EOF - more_set_headers "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' https://rybbit.viktorbarzin.me"; - EOF + source = "../ingress_factory" + namespace = kubernetes_namespace.privatebin.metadata[0].name + name = "privatebin" + host = "pb" + tls_secret_name = var.tls_secret_name + rybbit_site_id = "3ae810b0476d" + custom_content_security_policy = "script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' https://rybbit.viktorbarzin.me" } diff --git a/modules/kubernetes/real-estate-crawler/main.tf b/modules/kubernetes/real-estate-crawler/main.tf index b6ef374a..6b6bc7b1 100644 --- a/modules/kubernetes/real-estate-crawler/main.tf +++ b/modules/kubernetes/real-estate-crawler/main.tf @@ -211,38 +211,13 @@ resource "kubernetes_ingress_v1" "proxied-ingress" { name = "realestate-crawler" namespace = kubernetes_namespace.realestate-crawler.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/backend-protocol" = "http" - - # "nginx.ingress.kubernetes.io/auth-url" : var.protected ? "http://ak-outpost-authentik-embedded-outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/nginx" : null - # "nginx.ingress.kubernetes.io/auth-signin" : var.protected ? "https://authentik.viktorbarzin.me/outpost.goauthentik.io/start?rd=$scheme%3A%2F%2F$host$escaped_request_uri" : null - # "nginx.ingress.kubernetes.io/auth-snippet" : var.protected ? "proxy_set_header X-Forwarded-Host $http_host;" : null - - "nginx.ingress.kubernetes.io/configuration-snippet" = <<-EOF - limit_req_status 429; - limit_conn_status 429; - - # Rybbit Analytics - # Only modify HTML - sub_filter_types text/html; - sub_filter_once off; - - # Disable compression so sub_filter works - proxy_set_header Accept-Encoding ""; - - # Inject analytics before - sub_filter '' ' - - '; - EOF + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,realestate-crawler-rybbit-analytics@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } - - } spec { + ingress_class_name = "traefik" tls { hosts = ["wrongmove.viktorbarzin.me"] secret_name = var.tls_secret_name @@ -473,3 +448,25 @@ resource "kubernetes_cron_job_v1" "scrape-rightmove" { } } } + +# Rybbit analytics middleware for real-estate-crawler +resource "kubernetes_manifest" "rybbit_analytics" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "rybbit-analytics" + namespace = kubernetes_namespace.realestate-crawler.metadata[0].name + } + spec = { + plugin = { + rewritebody = { + rewrites = [{ + regex = "" + replacement = "" + }] + } + } + } + } +} diff --git a/modules/kubernetes/reverse_proxy/factory/main.tf b/modules/kubernetes/reverse_proxy/factory/main.tf index 5e758246..b194ff54 100644 --- a/modules/kubernetes/reverse_proxy/factory/main.tf +++ b/modules/kubernetes/reverse_proxy/factory/main.tf @@ -22,14 +22,6 @@ variable "max_body_size" { type = string default = "50m" } -variable "use_proxy_protocol" { - type = bool - default = true -} -variable "proxy_timeout" { - type = number - default = 60 -} variable "extra_annotations" { default = {} } @@ -37,8 +29,8 @@ variable "rybbit_site_id" { default = null type = string } -variable "additional_configuration_snippet" { - default = "" +variable "custom_content_security_policy" { + default = null type = string } @@ -70,94 +62,87 @@ resource "kubernetes_ingress_v1" "proxied-ingress" { name = var.name namespace = var.namespace annotations = merge({ - "nginx.ingress.kubernetes.io/backend-protocol" = "${var.backend_protocol}" - "kubernetes.io/ingress.class" = "nginx" - # "nginx.ingress.kubernetes.io/auth-url" : var.protected ? "https://oauth2.viktorbarzin.me/oauth2/auth" : null - # "nginx.ingress.kubernetes.io/auth-signin" : var.protected ? "https://oauth2.viktorbarzin.me/oauth2/start?rd=/redirect/$http_host$escaped_request_uri" : null - # Do not do hairpinning - # "nginx.ingress.kubernetes.io/auth-url" : var.protected ? "http://oauth2.oauth2.svc.cluster.local/oauth2/auth" : null - # "nginx.ingress.kubernetes.io/auth-signin" : var.protected ? "http://oauth2.oauth2.svc.cluster.local/oauth2/start?rd=/redirect/$http_host$escaped_request_uri" : null - - "nginx.ingress.kubernetes.io/auth-url" : var.protected ? "http://ak-outpost-authentik-embedded-outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/nginx" : null - # "nginx.ingress.kubernetes.io/auth-signin" : var.protected ? "https://authentik.viktorbarzin.me/outpost.goauthentik.io/start?rd=$scheme%3A%2F%2F$host$escaped_request_uri" : null - # "nginx.ingress.kubernetes.io/auth-signin" : var.protected ? "https://authentik.viktorbarzin.me/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri" : null - "nginx.ingress.kubernetes.io/auth-signin" : var.protected ? "https://authentik.viktorbarzin.me/outpost.goauthentik.io/start?rd=$escaped_request_uri" : null - "nginx.ingress.kubernetes.io/auth-response-headers" : var.protected ? "Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid" : null - "nginx.ingress.kubernetes.io/auth-snippet" : var.protected ? "proxy_set_header X-Forwarded-Host $http_host;" : null - - # # 2. Local Basic Auth Config - # nginx.ingress.kubernetes.io/auth-type: basic - # nginx.ingress.kubernetes.io/auth-secret: emergency-basic-auth - # nginx.ingress.kubernetes.io/auth-realm: "Authentik Down - Use Emergency Login" - - # # 3. The Fallback Magic - # nginx.ingress.kubernetes.io/configuration-snippet: | - # satisfy any; - # allow all; - - "nginx.ingress.kubernetes.io/proxy-body-size" : var.max_body_size - "nginx.ingress.kubernetes.io/use-proxy-protocol" : var.use_proxy_protocol - "nginx.ingress.kubernetes.io/proxy-connect-timeout" : var.proxy_timeout - "nginx.ingress.kubernetes.io/proxy-send-timeout" : var.proxy_timeout - "nginx.ingress.kubernetes.io/proxy-read-timeout" : var.proxy_timeout - - "nginx.ingress.kubernetes.io/configuration-snippet" = <<-EOF - limit_req_status 429; - limit_conn_status 429; - ${var.additional_configuration_snippet} - ${var.rybbit_site_id != null ? <<-JS - # Rybbit Analytics - # Only modify HTML - sub_filter_types text/html; - sub_filter_once off; - - # Disable compression so sub_filter works - proxy_set_header Accept-Encoding ""; - - # Inject analytics before - sub_filter '' ' - - '; - JS - : "" - } - EOF - - }, var.extra_annotations) -} - -spec { - tls { - hosts = ["${var.name}.viktorbarzin.me"] - secret_name = var.tls_secret_name + "traefik.ingress.kubernetes.io/router.middlewares" = join(",", compact([ + "traefik-rate-limit@kubernetescrd", + var.custom_content_security_policy == null ? "traefik-csp-headers@kubernetescrd" : null, + "traefik-crowdsec@kubernetescrd", + var.protected ? "traefik-authentik-forward-auth@kubernetescrd" : null, + var.rybbit_site_id != null ? "${var.namespace}-rybbit-analytics-${var.name}@kubernetescrd" : null, + var.custom_content_security_policy != null ? "${var.namespace}-custom-csp-${var.name}@kubernetescrd" : null, + ])) + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" + }, var.extra_annotations) } - rule { - host = "${var.name}.viktorbarzin.me" - http { - dynamic "path" { - # for_each = { for pr in var.ingress_path : pr => pr } - for_each = var.ingress_path - content { - path = path.value - backend { - service { + spec { + ingress_class_name = "traefik" + tls { + hosts = ["${var.name}.viktorbarzin.me"] + secret_name = var.tls_secret_name + } + rule { + host = "${var.name}.viktorbarzin.me" + http { + dynamic "path" { + for_each = var.ingress_path - name = var.name - port { - number = var.port + content { + path = path.value + backend { + service { + + name = var.name + port { + number = var.port + } } } } } } - # path { - # # path = var.ingress_path - # path = each.value - # } } } } + +# Rybbit analytics middleware (rewritebody plugin) - created per service when rybbit_site_id is set +resource "kubernetes_manifest" "rybbit_analytics" { + count = var.rybbit_site_id != null ? 1 : 0 + + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "rybbit-analytics-${var.name}" + namespace = var.namespace + } + spec = { + plugin = { + rewritebody = { + rewrites = [{ + regex = "" + replacement = "" + }] + } + } + } + } +} + +# Custom CSP headers middleware - created per service when custom_content_security_policy is set +resource "kubernetes_manifest" "custom_csp" { + count = var.custom_content_security_policy != null ? 1 : 0 + + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "custom-csp-${var.name}" + namespace = var.namespace + } + spec = { + headers = { + contentSecurityPolicy = var.custom_content_security_policy + } + } + } } diff --git a/modules/kubernetes/reverse_proxy/main.tf b/modules/kubernetes/reverse_proxy/main.tf index dd5fd4ef..7969b215 100644 --- a/modules/kubernetes/reverse_proxy/main.tf +++ b/modules/kubernetes/reverse_proxy/main.tf @@ -76,34 +76,28 @@ module "nas-files" { # https://idrac.viktorbarzin.me/ module "idrac" { - source = "./factory" - name = "idrac" - external_name = "idrac.viktorbarzin.lan" - port = 443 - tls_secret_name = var.tls_secret_name - backend_protocol = "HTTPS" - extra_annotations = { - # authentik causes 413; we don't need the header below - "nginx.ingress.kubernetes.io/auth-response-headers" : null - } - depends_on = [kubernetes_namespace.reverse-proxy] + source = "./factory" + name = "idrac" + external_name = "idrac.viktorbarzin.lan" + port = 443 + tls_secret_name = var.tls_secret_name + backend_protocol = "HTTPS" + extra_annotations = {} + depends_on = [kubernetes_namespace.reverse-proxy] } # Can either listen on https or http; can't do both :/ # TODO: Not working yet module "tp-link-gateway" { - source = "./factory" - name = "gw" - external_name = "gw.viktorbarzin.lan" - port = 443 - tls_secret_name = var.tls_secret_name - backend_protocol = "HTTPS" - depends_on = [kubernetes_namespace.reverse-proxy] - protected = true - extra_annotations = { - # authentik causes 413; we don't need the header below - "nginx.ingress.kubernetes.io/auth-response-headers" : null - } + source = "./factory" + name = "gw" + external_name = "gw.viktorbarzin.lan" + port = 443 + tls_secret_name = var.tls_secret_name + backend_protocol = "HTTPS" + depends_on = [kubernetes_namespace.reverse-proxy] + protected = true + extra_annotations = {} } # https://truenas.viktorbarzin.me/ diff --git a/modules/kubernetes/rybbit/main.tf b/modules/kubernetes/rybbit/main.tf index 8e30f113..ddce51ba 100644 --- a/modules/kubernetes/rybbit/main.tf +++ b/modules/kubernetes/rybbit/main.tf @@ -293,35 +293,13 @@ resource "kubernetes_ingress_v1" "rybbit" { namespace = kubernetes_namespace.rybbit.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/use-regex" = "true" - # Optional: enable SSL redirect - #"nginx.ingress.kubernetes.io/force-ssl-redirect" = "true" - - "nginx.ingress.kubernetes.io/configuration-snippet" = <<-EOF - limit_req_status 429; - limit_conn_status 429; - - # Rybbit Analytics - # Only modify HTML - sub_filter_types text/html; - sub_filter_once off; - - # Disable compression so sub_filter works - proxy_set_header Accept-Encoding ""; - - # Inject analytics before - sub_filter '' ' - - '; - EOF + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,rybbit-rybbit-analytics@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { - ingress_class_name = "nginx" + ingress_class_name = "traefik" tls { hosts = ["rybbit.viktorbarzin.me"] secret_name = var.tls_secret_name @@ -332,7 +310,8 @@ resource "kubernetes_ingress_v1" "rybbit" { http { # API backend path { - path = "/api(/|$)(.*)" + path = "/api" + path_type = "Prefix" backend { service { name = "rybbit" @@ -361,3 +340,25 @@ resource "kubernetes_ingress_v1" "rybbit" { } } } + +# Rybbit analytics middleware for self-tracking +resource "kubernetes_manifest" "rybbit_analytics" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "rybbit-analytics" + namespace = kubernetes_namespace.rybbit.metadata[0].name + } + spec = { + plugin = { + rewritebody = { + rewrites = [{ + regex = "" + replacement = "" + }] + } + } + } + } +} diff --git a/modules/kubernetes/send/main.tf b/modules/kubernetes/send/main.tf index 6469688b..6f4e0eb7 100644 --- a/modules/kubernetes/send/main.tf +++ b/modules/kubernetes/send/main.tf @@ -114,9 +114,5 @@ module "ingress" { name = "send" tls_secret_name = var.tls_secret_name port = 1443 - extra_annotations = { - "nginx.ingress.kubernetes.io/client-max-body-size" : "0" - "nginx.ingress.kubernetes.io/proxy-body-size" : "0", - } - rybbit_site_id = "c1b8f8aa831b" + rybbit_site_id = "c1b8f8aa831b" } diff --git a/modules/kubernetes/servarr/qbittorrent/main.tf b/modules/kubernetes/servarr/qbittorrent/main.tf index 2a473a3e..ed156773 100644 --- a/modules/kubernetes/servarr/qbittorrent/main.tf +++ b/modules/kubernetes/servarr/qbittorrent/main.tf @@ -140,7 +140,4 @@ module "ingress" { name = "qbittorrent" tls_secret_name = var.tls_secret_name protected = true - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-body-size" : "1G" // allow uploading .torrent files - } } diff --git a/modules/kubernetes/servarr/readarr/main.tf b/modules/kubernetes/servarr/readarr/main.tf index 68369b06..23f8844e 100644 --- a/modules/kubernetes/servarr/readarr/main.tf +++ b/modules/kubernetes/servarr/readarr/main.tf @@ -123,13 +123,13 @@ resource "kubernetes_ingress_v1" "readarr" { name = "readarr" namespace = "readarr" annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/auth-url" : "https://oauth2.viktorbarzin.me/oauth2/auth" - "nginx.ingress.kubernetes.io/auth-signin" : "https://oauth2.viktorbarzin.me/oauth2/start?rd=/redirect/$http_host$escaped_request_uri" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd,traefik-authentik-forward-auth@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["readarr.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/traefik/middleware.tf b/modules/kubernetes/traefik/middleware.tf new file mode 100644 index 00000000..4b3d9185 --- /dev/null +++ b/modules/kubernetes/traefik/middleware.tf @@ -0,0 +1,177 @@ +# Shared Traefik Middleware CRDs +# These are referenced by ingress resources via annotations like: +# "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd" + +# Rate limiting middleware +resource "kubernetes_manifest" "middleware_rate_limit" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "rate-limit" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + rateLimit = { + average = 5 + burst = 250 + } + } + } + + depends_on = [helm_release.traefik] +} + +# Authentik forward auth middleware +resource "kubernetes_manifest" "middleware_authentik_forward_auth" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "authentik-forward-auth" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + forwardAuth = { + address = "http://ak-outpost-authentik-embedded-outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/traefik" + trustForwardHeader = true + authResponseHeaders = [ + "X-authentik-username", + "X-authentik-uid", + "X-authentik-email", + "X-authentik-name", + "X-authentik-groups", + "Set-Cookie", + ] + } + } + } + + depends_on = [helm_release.traefik] +} + +# IP allowlist for local-only access +resource "kubernetes_manifest" "middleware_local_only" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "local-only" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + ipAllowList = { + sourceRange = [ + "192.168.1.0/24", + "10.0.0.0/8", + "fc00::/7", + "fe80::/10", + ] + } + } + } + + depends_on = [helm_release.traefik] +} + +# HTTPS redirect middleware +resource "kubernetes_manifest" "middleware_redirect_https" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "redirect-https" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + redirectScheme = { + scheme = "https" + permanent = true + } + } + } + + depends_on = [helm_release.traefik] +} + +# CSP headers middleware (default) +resource "kubernetes_manifest" "middleware_csp_headers" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "csp-headers" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + headers = { + contentSecurityPolicy = "frame-ancestors 'self' *.viktorbarzin.me viktorbarzin.me" + } + } + } + + depends_on = [helm_release.traefik] +} + +# CrowdSec bouncer plugin middleware +resource "kubernetes_manifest" "middleware_crowdsec" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "crowdsec" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + plugin = { + crowdsec-bouncer = { + crowdsecLapiKey = var.crowdsec_api_key + crowdsecLapiHost = "crowdsec-service.crowdsec.svc.cluster.local:8080" + crowdsecMode = "stream" + } + } + } + } + + depends_on = [helm_release.traefik] +} + +# TLS option for mTLS (client certificate auth) +resource "kubernetes_manifest" "tls_option_mtls" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "TLSOption" + metadata = { + name = "mtls" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + clientAuth = { + secretNames = ["ca-secret"] + clientAuthType = "RequireAndVerifyClientCert" + } + } + } + + depends_on = [helm_release.traefik] +} + +# Immich-specific rate limit (higher limits for photo uploads) +resource "kubernetes_manifest" "middleware_immich_rate_limit" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "immich-rate-limit" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + rateLimit = { + average = 100 + burst = 1000 + } + } + } + + depends_on = [helm_release.traefik] +} diff --git a/modules/kubernetes/tuya-bridge/main.tf b/modules/kubernetes/tuya-bridge/main.tf index 9ed60b19..bc154638 100644 --- a/modules/kubernetes/tuya-bridge/main.tf +++ b/modules/kubernetes/tuya-bridge/main.tf @@ -97,13 +97,4 @@ module "ingress" { namespace = kubernetes_namespace.tuya-bridge.metadata[0].name name = "tuya-bridge" tls_secret_name = var.tls_secret_name - - extra_annotations = { - "nginx.ingress.kubernetes.io/server-snippet" : <<-EOF - location /metrics { - deny all; - return 403; - } - EOF - } } diff --git a/modules/kubernetes/uptime-kuma/main.tf b/modules/kubernetes/uptime-kuma/main.tf index 2c5e410b..2a5a3402 100644 --- a/modules/kubernetes/uptime-kuma/main.tf +++ b/modules/kubernetes/uptime-kuma/main.tf @@ -98,9 +98,8 @@ module "ingress" { tls_secret_name = var.tls_secret_name service_name = "uptime-kuma" extra_annotations = { - "nginx.org/websocket-services" = "uptime-kuma" - "gethomepage.dev/enabled" = "true" - "gethomepage.dev/description" = "Uptime monitor" + "gethomepage.dev/enabled" = "true" + "gethomepage.dev/description" = "Uptime monitor" # gethomepage.dev/group: Media "gethomepage.dev/icon" : "uptime-kuma.png" "gethomepage.dev/name" = "Uptime Kuma" diff --git a/modules/kubernetes/vikunja/main.tf b/modules/kubernetes/vikunja/main.tf index 15360429..b0b1664e 100644 --- a/modules/kubernetes/vikunja/main.tf +++ b/modules/kubernetes/vikunja/main.tf @@ -200,11 +200,13 @@ resource "kubernetes_ingress_v1" "vikunja" { name = "vikunja" namespace = kubernetes_namespace.vikunja.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["todo.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/webhook_handler/main.tf b/modules/kubernetes/webhook_handler/main.tf index fc33c938..c449e7f6 100644 --- a/modules/kubernetes/webhook_handler/main.tf +++ b/modules/kubernetes/webhook_handler/main.tf @@ -194,11 +194,13 @@ resource "kubernetes_ingress_v1" "webhook_handler" { name = "webhook-handler-ingress" namespace = kubernetes_namespace.webhook-handler.metadata[0].name annotations = { - "kubernetes.io/ingress.class" = "nginx" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["webhook.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/xray/main.tf b/modules/kubernetes/xray/main.tf index b2538ddc..10c666e1 100644 --- a/modules/kubernetes/xray/main.tf +++ b/modules/kubernetes/xray/main.tf @@ -188,14 +188,13 @@ resource "kubernetes_ingress_v1" "ingress" { namespace = kubernetes_namespace.xray.metadata[0].name name = "xray" annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/backend-protocol" = "HTTP" - "nginx.org/websocket-services" : "xray" - "nginx.ingress.kubernetes.io/enable-access-log" = "false" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["xray-ws.viktorbarzin.me"] secret_name = var.tls_secret_name @@ -224,15 +223,14 @@ resource "kubernetes_ingress_v1" "ingress-grpc" { namespace = kubernetes_namespace.xray.metadata[0].name name = "xray-grpc" annotations = { - "kubernetes.io/ingress.class" = "nginx" - "nginx.ingress.kubernetes.io/enable-access-log" = "false" - "nginx.ingress.kubernetes.io/backend-protocol" = "GRPC" - "nginx.ingress.kubernetes.io/proxy-read-timeout" = "3600" - "nginx.ingress.kubernetes.io/proxy-send-timeout" = "3600" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" + "traefik.ingress.kubernetes.io/service.serversscheme" = "h2c" } } spec { + ingress_class_name = "traefik" tls { hosts = ["xray-grpc.viktorbarzin.me"] secret_name = var.tls_secret_name @@ -262,11 +260,13 @@ resource "kubernetes_ingress_v1" "ingress-vless" { namespace = kubernetes_namespace.xray.metadata[0].name name = "xray-vless" annotations = { - "kubernetes.io/ingress.class" = "nginx" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" } } spec { + ingress_class_name = "traefik" tls { hosts = ["xray-vless.viktorbarzin.me"] secret_name = var.tls_secret_name diff --git a/modules/kubernetes/youtube_dl/main.tf b/modules/kubernetes/youtube_dl/main.tf index 1721421c..156cc66a 100644 --- a/modules/kubernetes/youtube_dl/main.tf +++ b/modules/kubernetes/youtube_dl/main.tf @@ -126,10 +126,6 @@ module "ingress" { name = "ytdlp" tls_secret_name = var.tls_secret_name host = "yt" - extra_annotations = { - "nginx.ingress.kubernetes.io/client-max-body-size" : "0" - "nginx.ingress.kubernetes.io/proxy-body-size" : "0", - } } # ---------------------- @@ -321,8 +317,4 @@ module "highlights_ingress" { tls_secret_name = var.tls_secret_name host = "yt-highlights" protected = true - extra_annotations = { - "nginx.ingress.kubernetes.io/proxy-read-timeout" : "300" - "nginx.ingress.kubernetes.io/proxy-send-timeout" : "300" - } }