diff --git a/tools/station/deploy.sh b/tools/station/deploy.sh index ee64044..f7ca8cd 100755 --- a/tools/station/deploy.sh +++ b/tools/station/deploy.sh @@ -241,6 +241,64 @@ else echo "Hinweis: Schluesselverzeichnis $SIGN_DIR nicht gefunden - keine Migrationsimages erzeugt" >&2 fi +# Der Bildsatz "ohne-wlan" ist die SICHERE VORGABE: er geht im Automatikmodus an jede +# alte Klyqa-Lampe in Funkreichweite, auch an fremde, und das Flashen ist nicht +# umkehrbar. tools/release.sh prueft sein fertiges Archiv deshalb binaer gegen die Werte +# aus config.local.cmake (dort "Auf Zugangsdaten pruefen") - deploy.sh signierte +# dagegen ungeprueft, was in build-nocreds/ lag. +# +# Der Weg dorthin ist Bedienfehler, nicht Programmfehler: ein von Hand konfiguriertes +# build-nocreds/, oder ein gesetztes NINLIGHT_NOCREDS_BUILD_REL=build. Das +# Ergebnis waere, dass das Panel "ohne WLAN" anzeigt, waehrend das WLAN-Passwort des +# Bedieners unwiderruflich in fremde Lampen wandert - der teure, nicht rueckholbare +# Fehler. Lieber ein abgebrochener Deploy. +# +# Geprueft wird VOR dem Uebertragen, es ist also noch nichts beim Pi angekommen. Nur +# dieser eine Satz: migrate-wlan/ SOLL die Zugangsdaten enthalten, das ist sein Zweck. +# Der Pruefkoerper ist bis auf die Meldung derselbe wie in tools/release.sh. +if [ -d "$STAGE/migrate-nocreds" ]; then + echo "== Bildsatz 'ohne-wlan' auf Zugangsdaten pruefen ==" + LOCAL="$ROOT/config.local.cmake" + if [ -f "$LOCAL" ]; then + python3 - "$STAGE/migrate-nocreds" "$LOCAL" <<'CHECKPY' || exit 1 +import os +import re +import sys + +stage, local = sys.argv[1], sys.argv[2] +text = open(local, encoding="utf-8", errors="replace").read() +# Nur die drei Werte, die wirklich geheim sind. NINLIGHT_AP_PASSWORD und der +# Vorgabetoken stehen oeffentlich im Repo (README, Bruno) - sie hier mitzupruefen +# haette nur falsche Alarme erzeugt. +SECRETS = ("NINLIGHT_WIFI_SSID", "NINLIGHT_WIFI_PASSWORD", "NINLIGHT_TOKEN") +PUBLIC = {"", "ninlight", "aabbccddeeff0011223344", "ninlight-recovery", "change-me-please"} +needles = [] +for name in SECRETS: + m = re.search(r'^\s*set\s*\(\s*%s\s+"([^"]*)"' % name, text, re.M) + if m and m.group(1) not in PUBLIC: + needles.append((name, m.group(1).encode())) + +hits = [] +for root, _dirs, files in os.walk(stage): + for fn in sorted(files): + path = os.path.join(root, fn) + with open(path, "rb") as fh: + data = fh.read() + for name, needle in needles: + if needle in data: + hits.append("%s in %s" % (name, os.path.relpath(path, stage))) +if hits: + sys.stderr.write( + "Fehler: Zugangsdaten aus config.local.cmake im Bildsatz 'ohne-wlan' gefunden, " + "nichts uebertragen:\n " + "\n ".join(hits) + "\n") + sys.exit(1) +print(" sauber (%d geprueft)" % len(needles)) +CHECKPY + else + echo " uebersprungen - $LOCAL nicht vorhanden" >&2 + fi +fi + echo "== uebertragen ==" # -c (Inhaltsvergleich per Checksumme statt Groesse+Zeitstempel): ein neues # Image kann zufaellig dieselbe Byte-Groesse wie das alte haben, und bei diff --git a/tools/station/tests/test_deploy.sh b/tools/station/tests/test_deploy.sh index c5fc25d..3b65822 100755 --- a/tools/station/tests/test_deploy.sh +++ b/tools/station/tests/test_deploy.sh @@ -247,6 +247,61 @@ with open(path, 'wb') as fh: " "$path" } +# Wegwerf-Zugangsdaten fuer die Pruefung des Bildsatzes "ohne-wlan" - niemals die +# echten aus dem Repo. Dieselbe Rolle wie SECRET_* in tools/tests/test_release.sh. +SECRET_SSID="TestWLAN-9c2e-geheim" +SECRET_PASS="testpasswort-9c2e" +SECRET_TOKEN="0badc0de9c2e0badc0de9c2e" + +# Legt config.local.cmake im Sandbox-Repo an - genau die Datei, gegen die deploy.sh den +# Bildsatz "ohne-wlan" prueft. +write_local_cmake() { + local sandbox=$1 + cat >"$sandbox/config.local.cmake" < nichts uebertragen ===" SANDBOX="$TMPROOT/s1"; STUBBIN="$TMPROOT/bin1"; LOG="$TMPROOT/s1.log"; SANDBOX_STATE="$TMPROOT/s1-state" setup_sandbox "$SANDBOX" @@ -1271,6 +1326,99 @@ grep -Fq "systemctl enable ninlight-poweroff" "$LOG" \ [ ! -e /tmp/ninlight-poweroff.service ] && [ ! -e /tmp/ninlight-reboot.service ] \ && pass "die Ablage wurde aufgeraeumt" || fail "Unit-Dateien blieben unter /tmp liegen" +echo +echo "=== Szenario 33 (Abschlussreview, I3): Zugangsdaten im Bildsatz 'ohne-wlan' brechen den Deploy ab ===" +# Der Satz "ohne-wlan" ist die SICHERE VORGABE - im Automatikmodus geht er an jede alte +# Klyqa-Lampe in Funkreichweite, auch an fremde, und das Flashen ist nicht umkehrbar. +# release.sh prueft sein Archiv binaer gegen config.local.cmake; deploy.sh signierte +# bisher ungeprueft, was in build-nocreds/ lag. Ein von Hand konfiguriertes +# build-nocreds/ genuegt, damit das Panel "ohne WLAN" anzeigt, waehrend das +# WLAN-Passwort des Bedieners unwiderruflich in fremde Lampen wandert. +SANDBOX="$TMPROOT/s33"; STUBBIN="$TMPROOT/bin33"; LOG="$TMPROOT/s33.log"; SANDBOX_STATE="$TMPROOT/s33-state" +setup_sandbox "$SANDBOX" +setup_stubbin "$STUBBIN" "$LOG" +write_local_cmake "$SANDBOX" +mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build-nocreds/foo" +make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img" W +echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img" +# Der undichte Abzug: enthaelt SSID, Passwort und Token. +make_gz_file_with_creds "$SANDBOX/build-nocreds/foo/ninlight_foo_gz.img" N +# GEGENPROBE (ohne sie waere dieses Szenario wertlos): der "Compiler" dieser Suite muss +# die Zugangsdaten wirklich einbauen. Genau diese Gegenprobe gibt es in test_release.sh +# fuer den Docker-Stub - dort hat sie belegt, dass Fall 5 nicht nur deshalb gruen ist, +# weil nie jemand etwas einbaut. +if tree_contains "$SANDBOX/build-nocreds" "$SECRET_PASS"; then + pass "Gegenprobe: der Abzug in build-nocreds/ enthaelt das WLAN-Passwort wirklich" +else + fail "Gegenprobe fehlgeschlagen - der Abzug enthaelt gar keine Zugangsdaten, Szenario 33 waere wertlos" +fi +# Der Pi hat schon einen (sauberen) Bestand aus einem frueheren Deploy - er darf durch +# den Abbruch weder ersetzt noch geloescht werden. +mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds" +echo "alter-sauberer-abzug" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img" +: >"$LOG" +set +e +OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1) +RC=$? +set -e +[ "$RC" -ne 0 ] && pass "Exit-Code != 0 ($RC)" || fail "Exit-Code war 0 - der undichte Bildsatz waere ausgeliefert worden: $OUT" +echo "$OUT" | grep -q "Zugangsdaten" \ + && pass "Fehlermeldung nennt die gefundenen Zugangsdaten" \ + || fail "keine Fehlermeldung zu Zugangsdaten: $OUT" +echo "$OUT" | grep -q "NINLIGHT_WIFI_PASSWORD" \ + && pass "Fehlermeldung nennt den betroffenen Schluessel" \ + || fail "Fehlermeldung nennt den Schluessel nicht: $OUT" +if ! grep -q "STUB ssh\|STUB rsync\|STUB scp" "$LOG"; then + pass "kein ssh/rsync/scp aufgerufen - nichts wurde uebertragen" +else + fail "es wurde trotz gefundener Zugangsdaten uebertragen: $(cat "$LOG")" +fi +[ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img")" = "alter-sauberer-abzug" ] \ + && pass "der Bestand auf dem Pi blieb unangetastet" \ + || fail "der Bestand auf dem Pi wurde veraendert" +if ! tree_contains "$SANDBOX_STATE" "$SECRET_PASS"; then + pass "kein Zugangsdatum hat es in die Sandbox-Zielablage geschafft" +else + fail "Zugangsdaten sind trotz Abbruch beim Ziel gelandet" +fi + +echo +echo "=== Szenario 34 (Abgrenzung zu 33): ein sauberer Satz 'ohne-wlan' geht raus, migrate-wlan DARF die Zugangsdaten tragen ===" +# Die Pruefung muss genau einen Satz betreffen. migrate-wlan/ enthaelt die Zugangsdaten +# absichtlich - das ist sein ganzer Zweck ("Lampe haengt sofort im eigenen WLAN"). Eine +# Pruefung, die auch ihn abweist, waere unbenutzbar und wuerde nach kurzer Zeit +# abgeschaltet. +SANDBOX="$TMPROOT/s34"; STUBBIN="$TMPROOT/bin34"; LOG="$TMPROOT/s34.log"; SANDBOX_STATE="$TMPROOT/s34-state" +setup_sandbox "$SANDBOX" +setup_stubbin "$STUBBIN" "$LOG" +write_local_cmake "$SANDBOX" +mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build-nocreds/foo" +# build/ MIT Zugangsdaten (wie im echten Betrieb), build-nocreds/ ohne. +make_gz_file_with_creds "$SANDBOX/build/foo/ninlight_foo_gz.img" W +echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img" +make_gz_file "$SANDBOX/build-nocreds/foo/ninlight_foo_gz.img" N +: >"$LOG" +set +e +OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1) +RC=$? +set -e +[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC - die Pruefung weist einen sauberen Satz ab: $OUT" +[ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img" ] \ + && pass "der saubere Satz 'ohne-wlan' kam an" || fail "der saubere Satz 'ohne-wlan' fehlt: $OUT" +[ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" ] \ + && pass "migrate-wlan kam trotz einkompilierter Zugangsdaten an" \ + || fail "migrate-wlan wurde faelschlich mit abgewiesen: $OUT" +if tree_contains "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan" "$SECRET_PASS"; then + pass "Gegenprobe: migrate-wlan traegt die Zugangsdaten wirklich - die Pruefung haette anschlagen KOENNEN" +else + fail "Gegenprobe fehlgeschlagen - migrate-wlan traegt gar keine Zugangsdaten, die Abgrenzung waere wertlos" +fi +if ! tree_contains "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds" "$SECRET_PASS"; then + pass "und der Satz 'ohne-wlan' auf dem Pi ist frei davon" +else + fail "der Satz 'ohne-wlan' auf dem Pi enthaelt Zugangsdaten" +fi + echo if [ "$FAILED" -eq 0 ]; then echo "Alle Tests bestanden."