diff --git a/.env.example b/.env.example index 6b2c302..92a136a 100644 --- a/.env.example +++ b/.env.example @@ -140,6 +140,13 @@ NPM_ADMIN_PORT=81 # 127.0.0.1 trotzdem ueber den Proxy. Dann hier auf localhost stellen. NPM_API_HOST=127.0.0.1 +# Holt der Proxy Manager beim Start IP-Bereiche von AWS und Cloudflare? +# Gebraucht wird das nur fuer Zugriffslisten mit deren Adressbereichen. +# Vorgabe hier: false. In Netzen, die TLS aufbrechen oder Verbindungen +# kappen, stirbt der Dienst sonst an einer unbehandelten Ausnahme und +# startet endlos neu - sichtbar nur als "502 Bad Gateway". +NPM_IP_RANGES_FETCH=false + # ===================================================================== # TERMINAL-BROWSER (lokale Bedienung des Proxy Managers) # Fuer den Fall, dass Port 81 von aussen nicht erreichbar ist. diff --git a/README.md b/README.md index c21c5d4..0bffda9 100644 --- a/README.md +++ b/README.md @@ -554,6 +554,7 @@ Die wichtigsten Schalter: | `NPM_ENABLED` | `on`/`off` – soll der Proxy-Manager-Container überhaupt laufen? Siehe [12.4](#124-proxy-manager-ganz-abschalten-externer-nginx) | | `NPM_ADMIN_PORT` | Port der Proxy-Manager-Oberfläche (Vorgabe 81) – nur über `npm-port` bzw. das Menü ändern | | `NPM_API_HOST` | Name für die Schnittstelle (`127.0.0.1` oder `localhost`) – siehe [Kapitel 14.6](#146-netze-mit-zwangsproxy-api-fehler-der-keiner-ist) | +| `NPM_IP_RANGES_FETCH` | IP-Bereiche von AWS/Cloudflare holen (Vorgabe `false`) – siehe [Kapitel 10.1.2](#1012-der-dienst-stürzt-in-einer-schleife-ab) | | `NPM_ADMIN_URL` | Startseite des Terminal-Browsers; leer = automatisch aus `NPM_ADMIN_PORT` | | `BACKUP_DIR` | Ablage für **manuelle** Backups (Vorgabe `./backups`); geplante Jobs haben eigene Pfade | | `BACKUP_AUTO` | `on`/`off` – sollen die Backup-Jobs per cron laufen? | @@ -1254,6 +1255,62 @@ man erkennt, wer da geantwortet hat: 502 Proxy Error... ``` +### 10.1.2 Der Dienst stürzt in einer Schleife ab + +Sieht das Protokoll des Containers so aus, ist der `502` erklärt: + +``` +[IP Ranges] › info Fetching https://ip-ranges.amazonaws.com/ip-ranges.json +Uncaught Error: Client network socket disconnected before secure TLS connection was established + at TLSSocket.emit (node:events:497:7) +... node --abort_on_uncaught_exception ... index.js +❯ Starting backend ... +[und das immer wieder] +``` + +Der Proxy Manager holt beim Start IP-Bereiche von AWS und Cloudflare – +gebraucht wird das **nur** für Zugriffslisten mit deren Adressbereichen, in +einem internen Netz also nie. Der Code fängt Fehler dabei ab, aber dieser +kommt asynchron vom TLS-Socket und läuft am `catch` vorbei. Weil node mit +`--abort_on_uncaught_exception` läuft, bricht der Prozess ab, s6 startet ihn +neu – und nginx davor antwortet die ganze Zeit mit `502`. + +Ausgelöst wird das von Netzen, die die Verbindung **annehmen und dann kappen** +(aufbrechende Proxys, Firewalls). Scheitert der Abruf sauber – DNS-Fehler, +Verbindung abgelehnt –, passiert nichts; der Dienst läuft weiter. + +Abhilfe: + +```bash +./scripts/cmk-manage.sh config set NPM_IP_RANGES_FETCH false +./scripts/cmk-manage.sh up +``` + +Im Protokoll steht dann `IP Ranges fetch is disabled by environment variable`. +**Vorgabe in diesem Projekt ist `false`** – der Abruf bringt einem internen +Monitoring-Server nichts und kann ihn lahmlegen. + +> Ein Rest bleibt: `initTimer()` läuft unabhängig vom Schalter und versucht es +> **alle sechs Stunden** erneut. Soll auch das ausgeschlossen sein, zeigen die +> Namen ins Leere – dann scheitert der Abruf sauber und wird abgefangen: +> +> ```yaml +> extra_hosts: +> - "ip-ranges.amazonaws.com:127.0.0.1" +> - "www.cloudflare.com:127.0.0.1" +> ``` + +Die Fehlermeldung erkennt den Fall übrigens selbst – sie zählt Startversuche +und Ausnahmen im Containerprotokoll: + +``` + Der Dienst startet nicht nur - er stuerzt in einer Schleife ab. + Im Protokoll: 4 Startversuche, 4 unbehandelte Ausnahmen. + Die Ausnahmen kommen vom Abruf der IP-Bereiche bei AWS und + Cloudflare. ... Abschalten: + cmk-manage.sh config set NPM_IP_RANGES_FETCH false +``` + ### 10.2 Proxy-Host anlegen – ohne Browser Die Weboberfläche lässt sich im Terminal-Browser nur mühsam bedienen; Formulare diff --git a/README_en.md b/README_en.md index c114985..a74e98f 100644 --- a/README_en.md +++ b/README_en.md @@ -557,6 +557,7 @@ The most important settings: | `NPM_ENABLED` | `on`/`off` – should the Proxy Manager container run at all? See [12.4](#124-turning-the-proxy-manager-off-entirely-external-nginx) | | `NPM_ADMIN_PORT` | port of the Proxy Manager interface (default 81) – change only via `npm-port` or the menu | | `NPM_API_HOST` | name the API is addressed by (`127.0.0.1` or `localhost`) – see [chapter 14.6](#146-networks-with-a-mandatory-proxy-an-api-error-that-is-none) | +| `NPM_IP_RANGES_FETCH` | fetch IP ranges from AWS/Cloudflare (default `false`) – see [chapter 10.1.2](#1012-the-service-crashes-in-a-loop) | | `NPM_ADMIN_URL` | start page of the terminal browser; empty = derived from `NPM_ADMIN_PORT` | | `BACKUP_DIR` | location for **manual** backups (default `./backups`); scheduled jobs have their own paths | | `BACKUP_AUTO` | `on`/`off` – should the backup jobs run via cron? | @@ -1251,6 +1252,62 @@ line, which reveals who answered: 502 Proxy Error... ``` +### 10.1.2 The service crashes in a loop + +If the container log looks like this, the `502` is explained: + +``` +[IP Ranges] › info Fetching https://ip-ranges.amazonaws.com/ip-ranges.json +Uncaught Error: Client network socket disconnected before secure TLS connection was established + at TLSSocket.emit (node:events:497:7) +... node --abort_on_uncaught_exception ... index.js +❯ Starting backend ... +[over and over] +``` + +At startup the Proxy Manager fetches IP ranges from AWS and Cloudflare – needed +**only** for access lists using those address ranges, so never on an internal +network. The code does catch errors there, but this one arrives asynchronously +on the TLS socket and bypasses the `catch`. Since node runs with +`--abort_on_uncaught_exception`, the process aborts, s6 restarts it – and nginx +in front answers `502` the whole time. + +It is triggered by networks that **accept the connection and then cut it** +(TLS-intercepting proxies, firewalls). If the fetch fails cleanly – DNS error, +connection refused – nothing happens; the service keeps running. + +The fix: + +```bash +./scripts/cmk-manage.sh config set NPM_IP_RANGES_FETCH false +./scripts/cmk-manage.sh up +``` + +The log then says `IP Ranges fetch is disabled by environment variable`. +**This project defaults to `false`** – the fetch gains an internal monitoring +server nothing and can take it down. + +> One remainder: `initTimer()` runs regardless of the switch and retries **every +> six hours**. To rule that out as well, point the names nowhere – then the +> fetch fails cleanly and is caught: +> +> ```yaml +> extra_hosts: +> - "ip-ranges.amazonaws.com:127.0.0.1" +> - "www.cloudflare.com:127.0.0.1" +> ``` + +The error message recognises this case by itself – it counts start attempts and +exceptions in the container log: + +``` + Der Dienst startet nicht nur - er stuerzt in einer Schleife ab. + Im Protokoll: 4 Startversuche, 4 unbehandelte Ausnahmen. + Die Ausnahmen kommen vom Abruf der IP-Bereiche bei AWS und + Cloudflare. ... Abschalten: + cmk-manage.sh config set NPM_IP_RANGES_FETCH false +``` + ### 10.2 Creating a proxy host – without a browser The web interface is painful in a terminal browser, and forms with file uploads diff --git a/docker-compose.yml b/docker-compose.yml index a627957..2767fb6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,6 +68,14 @@ services: # REST-API laesst sich nicht ohne Weiteres nutzen. INITIAL_ADMIN_EMAIL: ${NPM_ADMIN_EMAIL:-} INITIAL_ADMIN_PASSWORD: ${NPM_ADMIN_PASSWORD:-} + # Beim Start holt der Proxy Manager sonst IP-Bereiche von AWS und + # Cloudflare. Gebraucht wird das nur fuer Zugriffslisten mit deren + # Adressbereichen - in einem internen Netz also nie. Wird die + # TLS-Verbindung dabei aufgebaut und dann gekappt (aufbrechender + # Proxy, Firewall), stirbt der Dienst an einer unbehandelten + # Ausnahme und startet in einer Schleife neu; nach aussen sieht man + # nur "502 Bad Gateway". + IP_RANGES_FETCH_ENABLED: ${NPM_IP_RANGES_FETCH:-false} volumes: - ${NPM_DATA_DIR:-./data/npm/data}:/data:Z - ${NPM_LE_DIR:-./data/npm/letsencrypt}:/etc/letsencrypt:Z diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh index f6268e1..a87f616 100755 --- a/scripts/cmk-manage.sh +++ b/scripts/cmk-manage.sh @@ -3309,6 +3309,7 @@ npm|NPM_ADMIN_EMAIL|text|Konto des Proxy Managers (E-Mail)||1 npm|NPM_ADMIN_PASSWORD|password|Kennwort des Proxy-Manager-Kontos||1 npm|NPM_ADMIN_PORT|port|Port der Adminoberflaeche|| npm|NPM_API_HOST|text|Name fuer die Schnittstelle (127.0.0.1 oder localhost)||1 +npm|NPM_IP_RANGES_FETCH|choice|IP-Bereiche von AWS/Cloudflare holen|false:true|1 npm|PROXY_DOMAIN|text|Vorgabe-Domain fuer Proxy-Hosts||1 npm|PROXY_FORWARD_HOST|text|Weiterleitungsziel (Host)|| npm|PROXY_FORWARD_PORT|port|Weiterleitungsziel (Port)|| @@ -3445,7 +3446,7 @@ settings_post_set() { cmd_cert_external --watch "$val" ;; CERT_EXTERNAL_DIR) log "Wirksam nach: $SELF up (das Verzeichnis wird erst dann eingehaengt)" ;; - CMK_IMAGE|CMK_SITE_ID|CMK_CONTAINER_NAME|NPM_IMAGE|NPM_CONTAINER_NAME|TZ|CMK_LIVESTATUS_TCP|NPM_DISABLE_IPV6|CMK_DATA_DIR|NPM_DATA_DIR|NPM_LE_DIR) + CMK_IMAGE|CMK_SITE_ID|CMK_CONTAINER_NAME|NPM_IMAGE|NPM_CONTAINER_NAME|TZ|CMK_LIVESTATUS_TCP|NPM_DISABLE_IPV6|CMK_DATA_DIR|NPM_DATA_DIR|NPM_LE_DIR|NPM_IP_RANGES_FETCH) log "Wirksam nach: $SELF up (Container werden neu erstellt)" ;; esac } @@ -3771,6 +3772,37 @@ json_str() { printf '%s' "$1" | tr ',' '\n' | sed -n "s/.*\"$2\"[[:space:]]*:[[: # certificate_id oder owner_user_id zu, weil dort ein _ vor dem id steht. json_ids() { printf '%s' "$1" | grep -o '"id":[0-9]*' | sed 's/.*://' ; } +# Stirbt der Dienst hinter nginx in einer Schleife? Das sieht von aussen +# aus wie "startet noch", hoert aber nie auf. Im Protokoll steht es. +npm_diagnose_backend() { + container_running "$NPM_CONTAINER_NAME" || return 0 + local log starts crash + log="$($RUNTIME logs --tail 200 "$NPM_CONTAINER_NAME" 2>&1 || true)" + starts="$(printf '%s\n' "$log" | grep -c 'Starting backend' || true)" + crash="$(printf '%s\n' "$log" | grep -c 'Uncaught Error' || true)" + [ "${starts:-0}" -ge 3 ] || [ "${crash:-0}" -ge 2 ] || return 0 + + printf ' + %sDer Dienst startet nicht nur - er stuerzt in einer Schleife ab.%s + Im Protokoll: %s Startversuche, %s unbehandelte Ausnahmen.\n' \ + "$C_YEL" "$C_RST" "${starts:-0}" "${crash:-0}" >&2 + + case "$log" in + *ip-ranges.amazonaws.com*|*"IP Ranges"*) + printf ' + Die Ausnahmen kommen vom Abruf der IP-Bereiche bei AWS und + Cloudflare. In Netzen, die TLS aufbrechen oder Verbindungen kappen, + faellt der Dienst darueber - und startet endlos neu. Abschalten: + %s config set NPM_IP_RANGES_FETCH false + %s up + Gebraucht wird der Abruf nur fuer Zugriffslisten mit den + Adressbereichen von AWS oder Cloudflare.\n' "$SELF" "$SELF" >&2 ;; + *) + printf ' Letzte Ausnahme im Protokoll: + %s\n' "$(printf '%s\n' "$log" | grep -A1 'Uncaught Error' | tail -1 | cut -c1-70)" >&2 ;; + esac +} + npm_api_token() { local base; base="$(npm_api_base)" if [ -z "${NPM_ADMIN_EMAIL:-}" ] || [ -z "${NPM_ADMIN_PASSWORD:-}" ]; then @@ -3911,11 +3943,12 @@ EOF printf ' Der Proxy Manager selbst hat geantwortet (%s) - aber der Dienst DAHINTER ist nicht erreichbar. Das ist normal, solange er noch startet: nach einem Neustart dauert es rund zehn Sekunden, - bis die Schnittstelle antwortet. - - Kurz warten und noch einmal, sonst ins Protokoll sehen: + bis die Schnittstelle antwortet.\n' "$srv" >&2 + npm_diagnose_backend + printf ' + Sonst ins Protokoll sehen: %s logs - %s proxy restart\n' "$srv" "$SELF" "$SELF" >&2 + %s proxy restart\n' "$SELF" "$SELF" >&2 else printf ' Die Antwort kam ueber einen Proxy (Via: %s), nicht direkt. Pruefen: %s noproxy\n' "$via" "$SELF" >&2