Zugangsdaten: .netrc ersetzt - Kennwoerter mit Sonderzeichen kamen

verstuemmelt an

Der Download schlug trotz richtiger Zugangsdaten fehl. Ursache gefunden:
die Uebergabe lief ueber eine temporaere .netrc, deren Format die Felder
durch Leerzeichen trennt. Ein Kennwort mit Leerzeichen, #, " oder
aehnlichem kam dadurch still verstuemmelt an - curl schickte etwas
anderes als hinterlegt, der Server antwortete 401, und die Meldung
lautete "Anmeldung abgelehnt", obwohl die Daten stimmten.

Gegen einen eigens gestarteten Testserver nachgemessen, Kennwort
'Pa ss#wo"rt$1':
    ohne Anmeldung            HTTP 401
    neu, curl --config -      HTTP 200
    alt, .netrc               HTTP 401

Alle dreizehn Aufrufstellen sind auf curl_auth umgestellt, das die
Angaben ueber "curl --config -" von der Standardeingabe liest und in
Anfuehrungszeichen setzt. Beliebige Zeichen sind damit moeglich, und die
Zugangsdaten stehen weiterhin nicht in der Prozessliste - geprueft.

Ausserdem sagt der Download jetzt, WORAN es liegt: vor dem Holen wird
der Status abgefragt und 401/403 (Anmeldung), 404 (Datei gibt es nicht)
und 000 (nicht erreichbar) getrennt gemeldet. Doppelte Kandidaten
werden nicht mehr zweimal probiert.

Dazu zwei Aergernisse aus der Bedienung beseitigt:
  * Der Assistent meldete nach "nur eintragen" die Abweichung als
    Problem, obwohl der Benutzer genau das gewollt hatte. Jetzt steht
    dort schlicht, dass Edition und Version gespeichert sind und kein
    Image geholt wurde.
  * Der Dialog hinter CMK_IMAGE/EDITION/VERSION war lang und erklaerte
    mehr, als er half. Er nennt jetzt in zwei Zeilen die Lage und stellt
    die Handlung voran.

Randnotiz zur Fehlersuche: die URL selbst war nie das Problem. Der
Server liefert fuer check-mk-enterprise-docker-2.4.0p9.tar.gz ein 401,
also "vorhanden, nur geschuetzt" - und fuer check-mk-pro-... ein 404,
denn "pro" heisst die Edition erst ab 2.5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-20 18:11:19 +02:00
co-authored by Claude Opus 5
parent e7eb1c3be9
commit c1371da7aa
3 changed files with 119 additions and 100 deletions
+11 -4
View File
@@ -757,8 +757,15 @@ Optionen:
./scripts/cmk-manage.sh image test --only download
```
Die Zugangsdaten werden über eine temporäre `.netrc` an `curl` übergeben und
erscheinen daher **nicht** in der Prozessliste. Fehlt `curl`, weicht der Test
Die Zugangsdaten werden über `curl --config -` von der Standardeingabe
übergeben und erscheinen daher **nicht** in der Prozessliste.
> **Kennwörter mit Sonderzeichen:** Bis dahin lief das über eine temporäre
> `.netrc`-Datei. Deren Format trennt die Felder durch Leerzeichen – ein
> Kennwort mit Leerzeichen, `#` oder Anführungszeichen kam dadurch still
> verstümmelt an, und die Anmeldung schlug scheinbar grundlos fehl.
> Gegen einen Testserver nachgemessen: bei einem Kennwort mit Leerzeichen,
> `#`, `"` und `$` liefert der neue Weg HTTP 200, der alte HTTP 401. Fehlt `curl`, weicht der Test
für die Registry auf `podman login` bzw. `docker login` aus – auch dann in eine
Wegwerfdatei, die gespeicherte Anmeldung des Systems bleibt unberührt.
@@ -867,8 +874,8 @@ sudo ./scripts/cmk-manage.sh image download \
--user <benutzer> --password <kennwort>
```
Die Zugangsdaten werden per temporaerer `.netrc`-Datei an `curl` uebergeben und
tauchen damit **nicht** in der Prozessliste auf.
Die Zugangsdaten werden über `curl --config -` übergeben und erscheinen
daher nicht in der Prozessliste.
**c) Bereits heruntergeladene Datei einspielen**
+11 -4
View File
@@ -763,8 +763,15 @@ Options:
./scripts/cmk-manage.sh image test --only download
```
The credentials are passed to `curl` through a temporary `.netrc` and therefore
do **not** appear in the process list. If `curl` is missing, the registry check
The credentials are passed to `curl` via `curl --config -` on standard input
and therefore do **not** appear in the process list.
> **Passwords with special characters:** this used to go through a temporary
> `.netrc` file. Its format separates fields by whitespace, so a password
> containing spaces, `#` or quotes arrived silently mangled and the login
> failed for no apparent reason. Measured against a test server: with a
> password containing a space, `#`, `"` and `$`, the new route returns HTTP
> 200 and the old one HTTP 401. If `curl` is missing, the registry check
falls back to `podman login` / `docker login` – into a throwaway file as well,
so the system's stored login is left untouched.
@@ -872,8 +879,8 @@ sudo ./scripts/cmk-manage.sh image download \
--user <user> --password <password>
```
The credentials are handed to `curl` through a temporary `.netrc` file and
therefore do **not** appear in the process list.
The credentials are handed to `curl` via `curl --config -` and therefore do
not appear in the process list.
**c) Loading a file you already downloaded**
+97 -92
View File
@@ -467,19 +467,23 @@ image_fetch_url() {
local host; host="$(printf '%s' "$url" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
if command -v curl >/dev/null 2>&1; then
local netrc=""
local -a auth=()
if [ -n "$user" ]; then
netrc="$(mktemp)"; chmod 600 "$netrc"
printf 'machine %s login %s password %s\n' "$host" "$user" "$pass" >"$netrc"
auth=(--netrc-file "$netrc")
fi
# Erst nachsehen, was der Server sagt - dann ist im Fehlerfall
# klar, ob es an der Anmeldung (401) oder am Namen (404) liegt.
local code; code="$(http_code "$url" "$user" "$pass")"
case "$code" in
200) : ;;
401|403) warn "Anmeldung abgelehnt (HTTP $code)"
printf ' %s\n' "$url" >&2
log "Zugangsdaten pruefen: $SELF image test --only download"
return 1 ;;
404) log "Nicht vorhanden (HTTP 404): $(basename "$url")"; return 1 ;;
000) warn "Nicht erreichbar: $url"; return 1 ;;
*) warn "Unerwartete Antwort (HTTP $code): $url"; return 1 ;;
esac
log "Lade $url"
if curl -fL --progress-bar ${auth[@]+"${auth[@]}"} -o "$out" "$url"; then
[ -n "$netrc" ] && rm -f "$netrc"
if curl_auth "$user" "$pass" -fL --progress-bar -o "$out" "$url"; then
return 0
fi
[ -n "$netrc" ] && rm -f "$netrc"
return 1
elif command -v wget >/dev/null 2>&1; then
warn "curl fehlt - verwende wget (die Zugangsdaten sind dabei kurzzeitig
@@ -581,6 +585,12 @@ download_url_candidates() { # edition version -> URLs, beste zuerst
done
}
# Kandidaten ohne Dopplungen - der aus dem Archiv gelesene Name ist oft
# derselbe wie der erste erzeugte; sonst wuerde er zweimal probiert.
download_url_list() { # edition version
download_url_candidates "$1" "$2" | awk '!seen[$0]++'
}
# ---------------------------------------------------------------------
# image pull - aus einer Registry holen
# ---------------------------------------------------------------------
@@ -701,7 +711,7 @@ image_cmd_download() {
z.B. 2.3.0p23 - '$version' funktioniert dort nicht." ;;
esac
edition_known "$edition" || die "Unbekannte Edition '$edition'."
mapfile -t urls < <(download_url_candidates "$edition" "$version")
mapfile -t urls < <(download_url_list "$edition" "$version")
fi
local target="" u
@@ -712,7 +722,6 @@ image_cmd_download() {
break
fi
rm -f "$target"; target=""
warn "Nicht gefunden/abgelehnt: $u"
done
if [ -z "$target" ]; then
@@ -891,28 +900,42 @@ image_cmd_status() {
# 1. Registry (registry.checkmk.com): Docker-Registry-v2-Anmeldung und
# ob das Konto Zugriff auf das Enterprise-Repository hat.
# 2. Downloadportal: HTTP-Basic-Anmeldung auf die Image-Tarballs.
# Die Zugangsdaten gehen per temporaerer .netrc an curl und tauchen
# Die Zugangsdaten gehen ueber "curl --config -" an curl und tauchen
# damit nicht in der Prozessliste auf.
# ---------------------------------------------------------------------
# Schreibt eine temporaere .netrc fuer die uebergebenen Hosts
netrc_make() { # user pass host [host ...] -> Pfad auf stdout
local user="$1" pass="$2"; shift 2
local f h; f="$(mktemp)"; chmod 600 "$f"
for h in "$@"; do
[ -n "$h" ] && printf 'machine %s login %s password %s\n' "$h" "$user" "$pass" >>"$f"
done
printf '%s\n' "$f"
# Zugangsdaten an curl uebergeben, ohne sie in die Prozessliste zu
# schreiben.
#
# Frueher lief das ueber eine temporaere .netrc. Deren Format trennt die
# Felder durch Leerzeichen - ein Kennwort mit Leerzeichen oder anderen
# Sonderzeichen kam dadurch still verstuemmelt an, und die Anmeldung
# schlug scheinbar grundlos fehl. "curl --config -" liest die Angaben von
# der Standardeingabe und nimmt sie in Anfuehrungszeichen, vertraegt also
# beliebige Zeichen.
_curl_cfg() { # user pass -> Konfigurationszeile auf stdout
local u="$1" p="$2"
u="${u//\\/\\\\}"; u="${u//\"/\\\"}"
p="${p//\\/\\\\}"; p="${p//\"/\\\"}"
printf 'user = "%s:%s"\n' "$u" "$p"
}
# HTTP-Status einer URL holen (optional mit .netrc)
http_code() { # url [netrc] [extra-header]
local url="$1" netrc="${2:-}" hdr="${3:-}"
curl_auth() { # user pass curl-argumente...
local u="$1" p="$2"; shift 2
if [ -n "$u" ]; then
_curl_cfg "$u" "$p" | curl --config - "$@"
else
curl "$@"
fi
}
# HTTP-Status einer URL holen
http_code() { # url [user] [pass] [extra-header]
local url="$1" u="${2:-}" p="${3:-}" hdr="${4:-}"
local -a a=(-sS -o /dev/null -w '%{http_code}' --max-time 30)
[ -n "$netrc" ] && a+=(--netrc-file "$netrc")
[ -n "$hdr" ] && a+=(-H "$hdr")
if [ -n "$hdr" ]; then a+=(-H "$hdr"); fi
local code
code="$(curl "${a[@]}" "$url" 2>/dev/null)" || code=000
code="$(curl_auth "$u" "$p" "${a[@]}" "$url" 2>/dev/null)" || code=000
printf '%s\n' "${code:-000}"
}
@@ -977,26 +1000,20 @@ test_registry_login() { # reg repo user pass needs_login -> 0 = alles gut
[ -n "$realm" ] || { test_result fail "Token-Dienst nicht ermittelbar" "$wa"; return 1; }
local realm_host; realm_host="$(printf '%s' "$realm" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
local netrc=""
if [ -z "$anon" ]; then netrc="$(netrc_make "$user" "$pass" "$api" "$realm_host")"; fi
local url="$realm?service=$service&scope=repository:$repo:pull"
local body tcode
if [ -n "$netrc" ]; then
body="$(curl -sS --netrc-file "$netrc" --max-time 30 "$url" 2>/dev/null || true)"
tcode="$(http_code "$url" "$netrc")"
else
body="$(curl -sS --max-time 30 "$url" 2>/dev/null || true)"
tcode="$(http_code "$url")"
fi
local body tcode tu="" tp=""
if [ -z "$anon" ]; then tu="$user"; tp="$pass"; fi
body="$(curl_auth "$tu" "$tp" -sS --max-time 30 "$url" 2>/dev/null || true)"
tcode="$(http_code "$url" "$tu" "$tp")"
if [ "$tcode" = "401" ] || [ "$tcode" = "403" ]; then
test_result fail "Anmeldung abgelehnt (HTTP $tcode)" \
"Benutzername/Kennwort des Checkmk-Kundenportals pruefen"
[ -n "$netrc" ] && rm -f "$netrc"; return 1
return 1
fi
if [ "$tcode" != "200" ]; then
test_result fail "Token-Abruf fehlgeschlagen (HTTP $tcode)"
[ -n "$netrc" ] && rm -f "$netrc"; return 1
return 1
fi
[ -n "$anon" ] && test_result ok "anonymer Zugriff moeglich" \
|| test_result ok "Anmeldung akzeptiert"
@@ -1007,7 +1024,7 @@ test_registry_login() { # reg repo user pass needs_login -> 0 = alles gut
[ -n "$token" ] || token="$(printf '%s' "$body" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
if [ -n "$token" ]; then
local lcode
lcode="$(http_code "https://$api/v2/$repo/tags/list" "" "Authorization: Bearer $token")"
lcode="$(http_code "https://$api/v2/$repo/tags/list" "" "" "Authorization: Bearer $token")"
case "$lcode" in
200) test_result ok "Zugriff auf '$repo' vorhanden" ;;
401|403) test_result fail "kein Zugriff auf '$repo' (HTTP $lcode)" \
@@ -1018,12 +1035,9 @@ test_registry_login() { # reg repo user pass needs_login -> 0 = alles gut
*) test_result warn "Repository-Pruefung nicht eindeutig (HTTP $lcode)" ;;
esac
fi
[ -n "$netrc" ] && rm -f "$netrc"
;;
*[Bb]asic*|"")
local netrc; netrc="$(netrc_make "$user" "$pass" "$api")"
local bcode; bcode="$(http_code "https://$api/v2/" "$netrc")"
rm -f "$netrc"
local bcode; bcode="$(http_code "https://$api/v2/" "$user" "$pass")"
case "$bcode" in
200) test_result ok "Anmeldung akzeptiert (Basic)" ;;
401|403) test_result fail "Anmeldung abgelehnt (HTTP $bcode)"; rc=1 ;;
@@ -1074,19 +1088,18 @@ test_download_login() { # baseurl edition version user pass
return 1
fi
local netrc; netrc="$(netrc_make "$user" "$pass" "$host")"
local -a urls=()
if [ -n "${CMK_DOWNLOAD_URL:-}" ]; then
urls=("$CMK_DOWNLOAD_URL")
elif [ -n "$version" ]; then
mapfile -t urls < <(download_url_candidates "$edition" "$version")
mapfile -t urls < <(download_url_list "$edition" "$version")
else
urls=("$base/")
fi
local u code seen401="" seen404=""
for u in "${urls[@]}"; do
code="$(http_code "$u" "$netrc")"
code="$(http_code "$u" "$user" "$pass")"
case "$code" in
200) test_result ok "Datei erreichbar (HTTP 200)" "$u"; rc=0; break ;;
401|403) seen401=1; test_result fail "Anmeldung abgelehnt (HTTP $code)" "$u" ;;
@@ -1095,8 +1108,6 @@ test_download_login() { # baseurl edition version user pass
*) test_result warn "unerwartete Antwort (HTTP $code)" "$u" ;;
esac
done
rm -f "$netrc"
if [ "$rc" != 0 ] && [ -z "$seen401" ] && [ -n "$seen404" ]; then
test_result ok "Anmeldung akzeptiert, aber kein Tarball unter diesem Namen (HTTP 404)"
printf ' %s\n' "Version pruefen (z.B. 2.3.0p23, kein '-latest') oder"
@@ -1206,14 +1217,8 @@ registry_get_token() { # api repo user pass [scope] -> Token auf stdout
[ -n "$realm" ] || return 0
local realm_host; realm_host="$(printf '%s' "$realm" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
local url="$realm?service=$service&scope=$scope"
local body netrc=""
if [ -n "$user" ]; then
netrc="$(netrc_make "$user" "$pass" "$api" "$realm_host")"
body="$(curl -sS --netrc-file "$netrc" --max-time 30 "$url" 2>/dev/null || true)"
rm -f "$netrc"
else
body="$(curl -sS --max-time 30 "$url" 2>/dev/null || true)"
fi
local body
body="$(curl_auth "$user" "$pass" -sS --max-time 30 "$url" 2>/dev/null || true)"
local t
t="$(printf '%s' "$body" | sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
[ -n "$t" ] || t="$(printf '%s' "$body" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
@@ -1224,14 +1229,10 @@ registry_tags() { # api repo user pass -> Tags, eine je Zeile
local api="$1" repo="$2" user="$3" pass="$4"
local token; token="$(registry_get_token "$api" "$repo" "$user" "$pass")"
local -a a=(-sS --max-time 45)
if [ -n "$token" ]; then
a+=(-H "Authorization: Bearer $token")
elif [ -n "$user" ]; then
local netrc; netrc="$(netrc_make "$user" "$pass" "$api")"
a+=(--netrc-file "$netrc")
fi
local tu="" tp=""
if [ -n "$token" ]; then a+=(-H "Authorization: Bearer $token"); else tu="$user"; tp="$pass"; fi
local body
body="$(curl "${a[@]}" "https://$api/v2/$repo/tags/list" 2>/dev/null || true)"
body="$(curl_auth "$tu" "$tp" "${a[@]}" "https://$api/v2/$repo/tags/list" 2>/dev/null || true)"
printf '%s' "$body" \
| tr ',' '\n' \
| sed -n 's/.*"tags"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p;s/^[[:space:]]*"\([^"]*\)".*/\1/p' \
@@ -1243,12 +1244,7 @@ download_versions() { # user pass -> Versionsverzeichnisse
local base="${CMK_DOWNLOAD_BASEURL:-https://download.checkmk.com/checkmk}"
local host; host="$(printf '%s' "$base" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
local -a a=(-sS --max-time 45 -L)
local netrc=""
if [ -n "$user" ]; then
netrc="$(netrc_make "$user" "$pass" "$host")"; a+=(--netrc-file "$netrc")
fi
local body; body="$(curl "${a[@]}" "$base/" 2>/dev/null || true)"
[ -n "$netrc" ] && rm -f "$netrc"
local body; body="$(curl_auth "$user" "$pass" "${a[@]}" "$base/" 2>/dev/null || true)"
printf '%s' "$body" \
| grep -oE 'href="[0-9]+\.[0-9]+\.[0-9]+[a-z0-9.]*/?"' \
| sed -E 's|href="([^"/]+)/?"|\1|' | sort -u
@@ -1267,11 +1263,9 @@ registry_catalog() { # api user pass -> Repository-Namen
local api="$1" user="$2" pass="$3"
local token; token="$(registry_get_token "$api" "" "$user" "$pass" "registry:catalog:*")"
local -a a=(-sS --max-time 45)
if [ -n "$token" ]; then a+=(-H "Authorization: Bearer $token")
elif [ -n "$user" ]; then
local netrc; netrc="$(netrc_make "$user" "$pass" "$api")"; a+=(--netrc-file "$netrc")
fi
curl "${a[@]}" "https://$api/v2/_catalog?n=1000" 2>/dev/null \
local tu="" tp=""
if [ -n "$token" ]; then a+=(-H "Authorization: Bearer $token"); else tu="$user"; tp="$pass"; fi
curl_auth "$tu" "$tp" "${a[@]}" "https://$api/v2/_catalog?n=1000" 2>/dev/null \
| tr ',' '\n' | grep -oE '"[a-z0-9][a-z0-9._/-]*"' | tr -d '"' \
| grep -v '^repositories$' | sed '/^$/d' | sort -u
}
@@ -1291,9 +1285,10 @@ registry_repo_status() { # api repo user pass -> "code anzahl_tags"
local api="$1" repo="$2" user="$3" pass="$4"
local token; token="$(registry_get_token "$api" "$repo" "$user" "$pass")"
local -a a=(-sS -o /tmp/.cmkrepo.$$ -w '%{http_code}' --max-time 30)
[ -n "$token" ] && a+=(-H "Authorization: Bearer $token")
local tu="" tp=""
if [ -n "$token" ]; then a+=(-H "Authorization: Bearer $token"); else tu="$user"; tp="$pass"; fi
local code
code="$(curl "${a[@]}" "https://$api/v2/$repo/tags/list" 2>/dev/null)" || code=000
code="$(curl_auth "$tu" "$tp" "${a[@]}" "https://$api/v2/$repo/tags/list" 2>/dev/null)" || code=000
local n=0
if [ "${code:-000}" = 200 ]; then n="$(tr ',' '\n' <"/tmp/.cmkrepo.$$" 2>/dev/null | grep -c '"' || echo 0)"; fi
rm -f "/tmp/.cmkrepo.$$"
@@ -4124,9 +4119,20 @@ Naechste Schritte:
return 0
fi
local cm
if cm="$(image_consistency_check)"; then
ui_msg "Fertig" \
if [ "$src" = setonly ]; then
ui_msg "Eingetragen" \
"Edition und Version sind gespeichert:
${CMK_EDITION:-?} / ${CMK_VERSION:-?}
Es wurde KEIN Image geholt - verwendet wird weiterhin:
${CMK_IMAGE:-<nicht gesetzt>}
Zum Holen den Assistenten erneut starten und dort
'Registry holen' oder 'Tarball herunterladen' waehlen."
return 0
fi
ui_msg "Fertig" \
"Verwendet wird jetzt:
${CMK_IMAGE:-<nicht gesetzt>}
@@ -4134,9 +4140,6 @@ Eingestellt: ${CMK_EDITION:-?} / ${CMK_VERSION:-?}
Wirksam wird das mit 'Stack steuern -> Stack starten'
(die Container werden dabei neu erstellt)."
else
ui_msg "Hinweis" "$cm"
fi
}
gui_image_versions() {
@@ -4232,19 +4235,21 @@ gui_settings_edit() {
cur="$(settings_value "$key")"
if [ "$typ" = info ]; then
local cm sel2
if cm="$(image_consistency_check)"; then
cm="Edition, Version und Image passen zusammen."
local sel2 lage
if image_consistency_check >/dev/null; then
lage="Edition, Version und Image passen zusammen."
else
lage="Achtung: verwendet wird ${CMK_IMAGE:-<nicht gesetzt>},
eingestellt sind aber ${CMK_EDITION:-?} / ${CMK_VERSION:-?}.
Gestartet wird immer das Image, nicht die Vorgaben."
fi
sel2="$(ui_menu "$key" \
"Aktuell: ${cur:-<leer>}
$cm
Diese drei Werte setzt der Assistent gemeinsam." \
wizard "Assistent starten (Image beschaffen)" \
angleichen "Vorgaben an das VERWENDETE Image angleichen" \
nichts "Nichts aendern")" || { load_env "${ENV_FILE:-$PROJECT_DIR/.env}"; return 0; }
$lage" \
wizard "Image beschaffen (Edition, Version, Bezugsweg)" \
angleichen "Vorgaben an das verwendete Image angleichen" \
nichts "Nichts aendern")" || { load_env "${ENV_FILE:-$PROJECT_DIR/.env}"; return 0; }
case "$sel2" in
wizard) gui_image_wizard || true ;;
angleichen)