From f40be6e4bd31d489c7224f50146ba6f4d5d597f8 Mon Sep 17 00:00:00 2001 From: Marco Wende Date: Mon, 21 Sep 2026 14:35:24 +0200 Subject: [PATCH] Docker: interner nginx statt socat (Host-Header -> localhost) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ursache des 404 bei Zugriff über IP/Proxy: .NET-HttpListener bedient den Prefix http://localhost:8077/ nur bei Host: localhost. socat (L4) reicht den originalen Host-Header durch -> 404. Jetzt sitzt ein winziger nginx im Container davor, der den Host-Header auf localhost umschreibt (Upstream [::1]:8077, IPv4 als Fallback). App-Code bleibt unverändert. Co-Authored-By: Claude Opus 4.8 --- docker/Dockerfile | 8 +++++--- docker/README.md | 8 +++++--- docker/docker-compose.yml | 3 ++- docker/docker-entrypoint.sh | 30 ++++++++++++------------------ docker/nginx.conf | 31 +++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 docker/nginx.conf diff --git a/docker/Dockerfile b/docker/Dockerfile index a1cb5fc..821936d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,10 +5,11 @@ # Env-Variablen bzw. das Entrypoint-Script - ohne Codeaenderung. FROM mcr.microsoft.com/powershell:7.4-debian-12 -# socat = Bridge 0.0.0.0:BRIDGE_PORT -> 127.0.0.1:APP_PORT (App bindet nur loopback). +# nginx = interner Reverse-Proxy, der den Host-Header auf "localhost" umschreibt +# (HttpListener-Prefix http://localhost:8077/ bedient nur Host: localhost). # git = fuer das optionale Policy-Git-Snapshot-Feature. RUN apt-get update \ - && apt-get install -y --no-install-recommends socat git ca-certificates \ + && apt-get install -y --no-install-recommends nginx git ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && pwsh -NoProfile -Command "Install-Module Microsoft.Graph.Authentication -Scope AllUsers -Force" @@ -19,7 +20,8 @@ COPY Start.ps1 ./ COPY src/ ./src/ COPY www/ ./www/ -# Entrypoint (neu, nur fuer den Container). +# Interner nginx-Reverse-Proxy (Host-Rewrite) + Entrypoint (neu, nur fuer Container). +COPY docker/nginx.conf /etc/nginx/nginx.conf COPY docker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh diff --git a/docker/README.md b/docker/README.md index 7f80470..9b70a8b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -9,9 +9,11 @@ Entrypoint-Script. > veröffentlichten Port (`8080`) an. HTTPS macht der Proxy. ## Wie es funktioniert -- Die App bindet unverändert auf `http://localhost:8077/` (nur containerintern). -- Ein **socat-Bridge** im Container leitet `0.0.0.0:8080 → 127.0.0.1:8077` — dadurch ist - die App von außen erreichbar, ohne den App-Code anzufassen. +- Die App bindet unverändert auf `http://localhost:8077/` (nur containerintern) und + bedient — bedingt durch .NET-`HttpListener` — **nur Requests mit `Host: localhost`**. +- Ein **interner nginx** (Port 8080) leitet auf die App weiter und schreibt dabei den + **`Host`-Header auf `localhost`** um. Ohne dieses Rewrite käme bei Zugriff über IP/Proxy + ein `404`. Der App-Code bleibt unangetastet. - Settings/Policy-Exporte liegen im Volume `/data` (`XDG_CONFIG_HOME=/data`, wird von `Get-AppDataDir` auf Linux automatisch genutzt). diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 532d004..73d3cc2 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,7 +10,7 @@ services: ports: - "8080:8080" # :8080 -> vom Reverse-Proxy anziehen volumes: - - im-data:/data # settings.json + policy-exports persistent + - /volume2/docker/intuneManager/data:/data # settings.json + policy-exports persistent environment: - XDG_CONFIG_HOME=/data - APP_PORT=8077 @@ -19,3 +19,4 @@ services: volumes: im-data: +ls \ No newline at end of file diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index c888bc4..689d341 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -1,36 +1,30 @@ #!/usr/bin/env bash -# Startet die App unveraendert (bindet auf localhost:APP_PORT) und leitet externe -# Verbindungen per socat-Bridge dorthin - so bleibt der App-Code unangetastet. +# Startet die App unveraendert (bindet auf localhost:APP_PORT) und stellt einen +# internen nginx davor, der den Host-Header auf "localhost" umschreibt. # -# HINWEIS: .NET-HttpListener bindet den Prefix "localhost" je nach System auf IPv4 -# ODER IPv6. Deshalb ermitteln wir zur Laufzeit, welche Loopback-Familie antwortet, -# und richten socat darauf aus (sonst "Connection refused"). +# WARUM nginx statt socat: .NET-HttpListener bedient den Prefix +# http://localhost:APP_PORT/ NUR bei Host: localhost. socat (reiner TCP-Proxy) +# reicht den originalen Host-Header durch -> von aussen 404. nginx schreibt den +# Host-Header um -> jeder Zugriffsweg funktioniert, ohne den App-Code zu aendern. set -e : "${APP_PORT:=8077}" -: "${BRIDGE_PORT:=8080}" # App im Hintergrund starten. -NoBrowser: im Container kein Desktop/open. pwsh -NoProfile -File /app/Start.ps1 -NoBrowser -Port "${APP_PORT}" & APP_PID=$! -# Warten bis der Listener antwortet und dabei IPv4 vs. IPv6 erkennen. -# Default = IPv6 (haeufigster Fall im Container); IPv4 wird bevorzugt, wenn erreichbar. -SOCAT_TARGET="TCP6:[::1]:${APP_PORT}" +# Best-effort warten, bis der App-Listener antwortet (IPv6 oder IPv4). +# nginx wuerde sonst kurz 502 liefern, bis die App oben ist. for i in $(seq 1 30); do - if (exec 3<>"/dev/tcp/127.0.0.1/${APP_PORT}") 2>/dev/null; then - exec 3>&-; SOCAT_TARGET="TCP4:127.0.0.1:${APP_PORT}"; break - fi - if (exec 3<>"/dev/tcp/::1/${APP_PORT}") 2>/dev/null; then - exec 3>&-; SOCAT_TARGET="TCP6:[::1]:${APP_PORT}"; break - fi - # App unerwartet beendet? Dann mit deren Exit-Code aussteigen. + if (exec 3<>"/dev/tcp/::1/${APP_PORT}") 2>/dev/null; then exec 3>&-; break; fi + if (exec 3<>"/dev/tcp/127.0.0.1/${APP_PORT}") 2>/dev/null; then exec 3>&-; break; fi kill -0 "$APP_PID" 2>/dev/null || { echo "[entrypoint] App-Prozess beendet - Abbruch."; wait "$APP_PID"; exit $?; } sleep 1 done -echo "[entrypoint] Bridge: 0.0.0.0:${BRIDGE_PORT} -> ${SOCAT_TARGET}" -socat "TCP-LISTEN:${BRIDGE_PORT},fork,reuseaddr" "${SOCAT_TARGET}" & +echo "[entrypoint] Starte nginx (Host-Rewrite -> localhost) auf :8080" +nginx -g 'daemon off;' & # Am Leben des App-Prozesses haengen (Container endet, wenn die App endet). wait "$APP_PID" diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..14138da --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,31 @@ +# Interner Reverse-Proxy im Container. +# Zweck: den Host-Header auf "localhost" umschreiben. Die App bindet den +# HttpListener-Prefix http://localhost:8077/ und bedient NUR Requests mit +# Host: localhost - ohne dieses Rewrite kaeme von aussen 404. +worker_processes 1; +events { worker_connections 1024; } + +http { + access_log /dev/stdout; + error_log /dev/stderr; + + # App-Listener: bevorzugt IPv6-Loopback (so bindet HttpListener "localhost" + # im Container), IPv4 als Fallback. + upstream imapp { + server [::1]:8077; + server 127.0.0.1:8077 backup; + } + + server { + listen 8080; + listen [::]:8080; + + location / { + proxy_pass http://imapp; + proxy_set_header Host localhost; # <- der entscheidende Punkt + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_read_timeout 300s; # Device-Code-Login/Graph koennen dauern + } + } +}