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
This commit is contained in:
Viktor Barzin 2026-02-07 13:25:49 +00:00
parent 43cdebe791
commit b36932f9a3
50 changed files with 516 additions and 1681 deletions

View file

@ -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"
}

View file

@ -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"
}
}

View file

@ -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"
}

View file

@ -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

View file

@ -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 </head>
sub_filter '</head>' '
<script src="https://rybbit.viktorbarzin.me/api/script.js"
data-site-id="da853a2438d0"
defer></script>
</head>';
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 = "</head>"
replacement = "<script src=\"https://rybbit.viktorbarzin.me/api/script.js\" data-site-id=\"da853a2438d0\" defer></script></head>"
}]
}
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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"
}

View file

@ -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

View file

@ -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
}

View file

@ -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"
}
}

View file

@ -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"
}

View file

@ -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

View file

@ -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"
}
}

View file

@ -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
}
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -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:

View file

@ -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" : <<EOF
directio 4m;
sendfile off;
aio on;
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 </head>
sub_filter '</head>' '
<script src="https://rybbit.viktorbarzin.me/api/script.js"
data-site-id="35eedb7a3d2b"
defer></script>
</head>';
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 = "</head>"
replacement = "<script src=\"https://rybbit.viktorbarzin.me/api/script.js\" data-site-id=\"35eedb7a3d2b\" defer></script></head>"
}]
}
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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
}

View file

@ -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"

View file

@ -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"
}
}

View file

@ -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" {

View file

@ -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")
}
}

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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 </head>
sub_filter '</head>' '
<script src="https://rybbit.viktorbarzin.me/api/script.js"
data-site-id="edee05de453d"
defer></script>
</head>';
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 = "</head>"
replacement = "<script src=\"https://rybbit.viktorbarzin.me/api/script.js\" data-site-id=\"edee05de453d\" defer></script></head>"
}]
}
}
}
}
}

View file

@ -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 </head>
sub_filter '</head>' '
<script src="https://rybbit.viktorbarzin.me/api/script.js"
data-site-id="${var.rybbit_site_id}"
defer></script>
</head>';
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 = "</head>"
replacement = "<script src=\"https://rybbit.viktorbarzin.me/api/script.js\" data-site-id=\"${var.rybbit_site_id}\" defer></script></head>"
}]
}
}
}
}
}
# 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
}
}
}
}

View file

@ -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/

View file

@ -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 </head>
sub_filter '</head>' '
<script src="https://rybbit.viktorbarzin.me/api/script.js"
data-site-id="3c476801a777"
defer></script>
</head>';
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 = "</head>"
replacement = "<script src=\"https://rybbit.viktorbarzin.me/api/script.js\" data-site-id=\"3c476801a777\" defer></script></head>"
}]
}
}
}
}
}

View file

@ -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"
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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]
}

View file

@ -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
}
}

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"
}
}