diff --git a/README.md b/README.md index f0d9182..a81703d 100644 --- a/README.md +++ b/README.md @@ -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 --password ``` -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** diff --git a/README_en.md b/README_en.md index 73b189e..19a5d02 100644 --- a/README_en.md +++ b/README_en.md @@ -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 --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** diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh index 6fd2a99..1129879 100755 --- a/scripts/cmk-manage.sh +++ b/scripts/cmk-manage.sh @@ -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:-} + +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:-} @@ -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:-}, +eingestellt sind aber ${CMK_EDITION:-?} / ${CMK_VERSION:-?}. +Gestartet wird immer das Image, nicht die Vorgaben." fi sel2="$(ui_menu "$key" \ "Aktuell: ${cur:-} -$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)