From a21d4a442486df15d5f7001cfb2e25985101c86c Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 18 Apr 2026 23:24:25 +0000 Subject: [PATCH] =?UTF-8?q?[owntracks]=20Fix=20Service=20port=20scheme=20(?= =?UTF-8?q?https=E2=86=92http),=20unbreak=20phone=20POSTs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context iOS Owntracks app has been unable to upload for months — phone buffer now holds ~1200 pending points. Last successful `.rec` write was 2026-01-02T14:32:00Z, matching when the failures started. ### The 500 — verified in Traefik access log ``` 152.37.101.156 - viktor "POST /pub HTTP/1.1" 500 21 "-" "-" 47900 "owntracks-owntracks-owntracks-viktorbarzin-me@kubernetes" "https://10.10.107.194:8083" 84ms ``` Basic-auth + middleware chain (rate-limit, csp, crowdsec) all pass. Traefik then opens backend connection to `https://10.10.107.194:8083`. The Recorder pod listens **plain HTTP** on :8083 (`OTR_PORT=0` disables HTTPS in ot-recorder), so the TLS handshake never completes → 500. ### Root cause — Service port spec `kubernetes_service.owntracks` declared the port as: ``` name: https port: 443 targetPort: 8083 ``` Traefik's IngressClass scheme inference: if the Service port is named `https` OR numbered `443`, Traefik speaks HTTPS to that backend. Both were true here, pointing at a plain-HTTP socket. The name/number were purely cosmetic — a leftover from mirroring the external `:443` edge — and worked only while Traefik's default happened to be HTTP. A Traefik upgrade (or middleware-chain change) tightened inference and surfaced the mismatch. ## This change Rename port to `name=http, port=80` and update the matching Ingress backend `port.number` from 443 to 80. `targetPort` stays at 8083. ``` Phone -----> CF tunnel -----> Traefik (:443, TLS) -----> Service \ :80 (http) \ | \ v ---------------> Pod :8083 (plain HTTP hop) (HTTP listener) ``` Deployment container port label also renamed `https` → `http` for consistency (no functional effect — just readability). ## What is NOT in this change - **Not** switching the Recorder pod to HTTPS natively. That would require mounting a cert + rotation plumbing. External TLS is already terminated at Cloudflare/Traefik; in-cluster hop to the pod is plain-HTTP by design. - **Not** enabling `OTR_HTTPHOOK` to bridge Recorder → Dawarich (follow-up: code-z9b). - **Not** backfilling historical `.rec` files into Dawarich (follow-up: code-h2r). - Incidental: `providers.tf` + `.terraform.lock.hcl` refreshed by `terraform init -upgrade` to pick up the goauthentik provider that the ingress_factory module recently started requiring. ## Test Plan ### Automated ``` $ ../../scripts/tg plan Plan: 0 to add, 3 to change, 0 to destroy. $ ../../scripts/tg apply --non-interactive Apply complete! Resources: 0 added, 3 changed, 0 destroyed. $ kubectl -n owntracks get svc owntracks -o=jsonpath='{.spec.ports[0]}' {"name":"http","port":80,"protocol":"TCP","targetPort":8083} $ kubectl -n owntracks get ingress owntracks -o=jsonpath='{.spec.rules[0].http.paths[0].backend}' {"service":{"name":"owntracks","port":{"number":80}}} ``` ### Manual Verification In-cluster auth'd POST through the full ingress chain: ``` VIKTOR_PW=$(vault kv get -field=credentials secret/owntracks | jq -r .viktor) kubectl -n owntracks run curltest --rm -i --image=curlimages/curl --restart=Never -- \ curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST -u "viktor:$VIKTOR_PW" \ -H "Content-Type: application/json" \ -d '{"_type":"location","lat":0,"lon":0,"tst":1000000000,"tid":"vb"}' \ https://owntracks.viktorbarzin.me/pub # HTTP 200 ``` (previously: HTTP 500 on identical request) ### Reproduce locally 1. `vault login -method=oidc` 2. `cd infra/stacks/owntracks && ../../scripts/tg plan` 3. Expected: `Plan: 0 to add, 3 to change, 0 to destroy.` (or empty if already applied) 4. Watch next iOS Owntracks POST → Traefik access log should show `200`, not `500`. Closes: code-nqd Co-Authored-By: Claude Opus 4.7 (1M context) --- stacks/owntracks/.terraform.lock.hcl | 8 ++++++++ stacks/owntracks/main.tf | 11 +++++++---- stacks/owntracks/providers.tf | 4 ++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/stacks/owntracks/.terraform.lock.hcl b/stacks/owntracks/.terraform.lock.hcl index a1ca7484..fabbc047 100644 --- a/stacks/owntracks/.terraform.lock.hcl +++ b/stacks/owntracks/.terraform.lock.hcl @@ -24,6 +24,14 @@ provider "registry.terraform.io/cloudflare/cloudflare" { ] } +provider "registry.terraform.io/goauthentik/authentik" { + version = "2024.12.1" + constraints = "~> 2024.10" + hashes = [ + "h1:roBMd+gi+TGgikH/bMzEI8JfvJiMAQWt+8FmokCrQIs=", + ] +} + provider "registry.terraform.io/hashicorp/helm" { version = "3.1.1" hashes = [ diff --git a/stacks/owntracks/main.tf b/stacks/owntracks/main.tf index 5af77559..40106565 100644 --- a/stacks/owntracks/main.tf +++ b/stacks/owntracks/main.tf @@ -146,7 +146,7 @@ resource "kubernetes_deployment" "owntracks" { image = "owntracks/recorder:1.0.1" name = "owntracks" port { - name = "https" + name = "http" container_port = 8083 } env { @@ -202,8 +202,11 @@ resource "kubernetes_service" "owntracks" { app = "owntracks" } port { - name = "https" - port = 443 + # Recorder listens plain HTTP on 8083 (OTR_PORT=0 disables HTTPS). + # Port name/number drive Traefik's backend-scheme inference — must be + # http/80 so it doesn't try TLS against a plain socket (previous 500s). + name = "http" + port = 80 target_port = 8083 protocol = "TCP" } @@ -216,7 +219,7 @@ module "ingress" { namespace = kubernetes_namespace.owntracks.metadata[0].name name = "owntracks" tls_secret_name = var.tls_secret_name - port = 443 + port = 80 extra_annotations = { "traefik.ingress.kubernetes.io/router.middlewares" = "owntracks-basic-auth@kubernetescrd,traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" "gethomepage.dev/enabled" = "true" diff --git a/stacks/owntracks/providers.tf b/stacks/owntracks/providers.tf index b337a2e9..012af700 100644 --- a/stacks/owntracks/providers.tf +++ b/stacks/owntracks/providers.tf @@ -9,6 +9,10 @@ terraform { source = "cloudflare/cloudflare" version = "~> 4" } + authentik = { + source = "goauthentik/authentik" + version = "~> 2024.10" + } } }