Projekt

Obecné

Profil

Chyba #169

otevřený

dev.noveknihy.local: login padá na chybějícím klíči slepého indexu, dev DB zaostává za kódem

Přidáno uživatelem Šimon Formánek před asi 10 hodin(y). Aktualizováno před asi 8 hodin(y).

Stav:
Nový
Priorita:
Normální
Přiřazeno:
-
Začátek:
2026-11-04
Uzavřít do:
% Hotovo:

0%

Odhadovaná doba:
Strávený čas:

Popis

h2. Příznak

@POST http://dev.noveknihy.local/login.php?action=process@ vracel HTTP 200 s fatální chybou v těle:

Fatal error: Uncaught RuntimeException: BlindIndex: key file not readable:
/home/f/git/noveknihy/catalog/includes/local/index_keys.php
in /home/f/git/noveknihy/catalog/includes/classes/Noveknihy/BlindIndex.php:100
#2 .../includes/modules/content/login/cm_login_form.php(42): Noveknihy\BlindIndex::whereEmail()

Pozor na reprodukci: bez platného @formid@ se @Form::validate_action_is('process')@ neprojde a @cm_login_form::login()@ se vůbec nezavolá, takže se chyba neprojeví. Je potřeba vzít token z @GET /login.php@ se stejnou cookie @ceid@.

h2. Příčina

Není to chyba v kódu. Pracovní kopie @/home/f/git/noveknihy@ (větev @functionalHTML@) byla napřed proti databázi @noveknihy_dev@. Ověřeno:

  • @/home/f/git/noveknihy/catalog/includes/local/index_keys.php@ neexistoval
  • sloupec @customers.customers_email_bidx@ neexistoval, @customers_email_address@ byl plaintext @varchar(255)@ s čitelnými adresami (10 zákazníků)
  • tabulky @role_keys@, @role_members@ neexistovaly
  • @php vendor/bin/phinx status -c phinx-adapter.php@ hlásil 16 migrací @down@ — od @20261016150000 AKeyIsWrappedForEveryReader@ po @20261026100000 AGuestBuysWithoutBeingSignedIn@, tedy celou vrstvu šifrování osobních údajů

@cm_login_form::login()@ volá @BlindIndex::whereEmail()@ bezpodmínečně. Dokumentace @/home/f/git/noveknihy/doc/cs/sifrovani-osobnich-udaju.md:745@ to říká přímo: "Dokud @--seal@ u zákazníků neproběhl, nikdo se nepřihlásí."

h2. Druhý zasažený subsystém

Stejná příčina shazovala i agentní API: @/home/f/git/noveknihy/catalog/includes/classes/agent_auth.php:38@ selectuje sloupec @pii_key_token@, který vzniká až migrací @20261026040000 AnAgentCarriesTheCustomersKeyInItsToken@. Jakýkoli předložený token tedy skončil fatální chybou místo JSON odpovědi.

h2. Co už je udělané (4. 11. 2026 večer)

Záloha databáze před zásahem: @/home/f/zalohy-noveknihy/noveknihy_dev-pred-sifrovanim-2026-11-04-2352.sql.gz@ (62 MB, práva 0600). Integrita gzipu, přítomnost klíčových tabulek i úplnost dumpu ověřeny. Záměrně mimo repozitář — @/home/f/git/noveknihy/db/data/@ není v @.gitignore@ a dump obsahuje zákaznická data.

Všech 16 migrací spuštěno, @phinx status@ nehlásí nic @down@. Vzniklo @customers_email_bidx@, @pii_key_token@, @role_keys@, @role_members@. Migrace mimochodem zvedly minimální délku hesla z 5 na 8 a zapnuly brzdy @ar_login.php@ a @ar_admin_login.php@.

Vygenerován a zapsán indexový klíč @/home/f/git/noveknihy/catalog/includes/local/index_keys.php@ (32 bajtů, verze 1, práva 0600). Před generováním ověřeno, že žádný starší nikde neleží — ty v @/home/nkcrypto/keys/@ a @/home/nkcryptoadmin/keys/@ patří crypto simulaci na jiné databázi @noveknihy_crypto@ (uživatelé @nkc_shop@ / @nkc_admin@), a záloha @/home/f/noveknihy-zaloha-klicu-NA-PAPIR.txt@ je ke značkovacímu klíči e-knih a platebním klíčům GP webpay. Ověřeno také, že web ten adresář nevydává: @includes/local/.htaccess@ má @Require all denied@ a všechny zkoušené cesty vrací 403.

Fatální chyba v loginu je tím pryč — stránka vrací korektní "E-mailová adresa nebo heslo nenalezeno." Agentní API vrací korektní JSON 401 místo stack trace.

h2. Co zbývá

Obřad s klíči podle @/home/f/git/noveknihy/doc/cs/sifrovani-osobnich-udaju.md:684-745@. Vyžaduje heslo administrátora, které zadá člověk, protože se pod ním zabalí privátní klíč role. Připraven skript @/home/f/obrad-dev-klice.sh@ (práva 0700, heslo čte bez echa, takže neskončí v historii shellu):

  • administrátor @devkeys@
  • @crypto-keys.php init@ — záchranný klíč + role @root@ a @secretariat@
  • @crypto-keys.php shop-key@ — role @shop@; bez její veřejné půlky se nezapečetí nic a nikdo se nezaregistruje ani nenakoupí (@RoleKeys::publicKeyOf()@ vyhodí @no role 'shop'@). Soubor @shop_private.php@ vzniknout nesmí, privátní půlka zůstane zapečetěná na záchranný klíč v @role_keys.wrapped_private@. Pořadí je dané: @RoleKeys::createRole()@ na @RoleKeys.php:133-138@ odmítne pracovat bez záchranného klíče, takže @init@ → @shop-key@.
  • @crypto-seal-{customers,orders,outgoing}.php --seal@ a @--verify@

Nevratný @--blank@ skript záměrně nedělá, je na samostatné rozhodnutí po kontrole.

h2. Aktuálně rozbité, než obřad doběhne

  • @POST /create_account.php@ padá na @RuntimeException: RoleKeys: no role 'shop'@ (@RoleKeys.php:78@ ← @CustomerPii.php:135@ ← @cd_city.php:130@)
  • přihlásit se nikdo nemůže, @customers_email_bidx@ je u všech 10 zákazníků prázdný

Prohlížení, hledání, bazar, košík i vykreslení @login.php@ fungují — ověřeno sedm stránek, fatální chyba nikde.

h2. Prostředí

Stroj @mi@, uživatel @Šimon Formánek@. DocumentRoot dev instance je @/home/f/git/noveknihy/catalog/@, FPM pool @noveknihy-dev@ běží pod @user = f@. Dev administrace je na @http://dev.noveknihy.local/admin/@ a používá tutéž databázi i tentýž @includes/local/@, takže jeden @index_keys.php@ pokrývá obchod i admin.


Související úkoly 1 (1 otevřený — 0 uzavřených)

související s Požadavek #170: Android aplikace nkBookImport: focení knihy a import do bazaru + agentní endpointyNový2026-11-04

Akce

Aktualizováno uživatelem Šimon Formánek před asi 10 hodin(y)

  • související s Požadavek #170: Android aplikace nkBookImport: focení knihy a import do bazaru + agentní endpointy přidán

Aktualizováno uživatelem Šimon Formánek před asi 8 hodin(y)

Obřad s klíči dokončen 5. 11. 2026 ve 02:01, oprava ověřena přes HTTP od začátku do konce.

h3. Co obřad udělal

  • administrátor @devkeys@, id 24
  • @init@ -- záchranný klíč vytvořen, role @root@ a @secretariat@, devkeys zapsán do obou
  • @shop-key@ -- role @shop@ vytvořena, privátní půlka zahozena (soubor @shop_private.php@ nevznikl, a to je správně)
  • @--seal@ -- 12 zákazníků a 12 adres, 4 objednávky, 26 řádků fronty pošty
  • @--verify@ -- 12 zákazníků, 4 objednávky, 26 řádků, všechno se přečetlo zpět shodně
  • @--rewrap@ -- 26 obálek pro sekretariát dodáno (dokumentovaný krok, který skript sám nedělal)

Role v @role_keys@: @recovery@, @root@, @secretariat@, @shop@. V @role_members@ je admin 24 v rolích @root@ a @secretariat@.

h3. Ověření opravy

| Co | Před | Po |
| @POST /login.php?action=process@ | fatální @BlindIndex: key file not readable@ | 302 na @/cs/@, přihlášení projde |
| @POST /create_account.php@ | fatální @RoleKeys: no role 'shop'@ | 302 na @create_account_success.php@ |
| @account.php@ přihlášeného | -- | 200, zobrazí jméno zákazníka |
| špatné heslo | -- | odmítnuto, "E-mailová adresa nebo heslo nenalezeno." |
| @/agent/search.php@ s tokenem | stack trace | JSON 401 @invalid_token@ |

Slepý index ověřen adresně: @f@simonformanek.cz@ najde id 19, @F@SimonFormanek.CZ@ i adresa s mezerami okolo najdou tentýž účet (normalizace na malá písmena a trim funguje), neexistující adresa se nenajde.

Zaregistrován testovací zákazník, přihlášen, ověřeno a smazán. U nově registrovaného zákazníka jsou plaintextové sloupce prázdné (0 bajtů) a existují jen zapečetěné -- registrace pečetí správně. Dešifrování prokázáno přímo: s cookie @nkCustomerKey@ se jméno v @account.php@ zobrazí, bez ní ne.

Stav: 800 PHPUnit testů prochází, 0 migrací @down@, žádná stránka nehlásí fatální chybu (ověřeno osm stránek včetně @/admin/@).

h3. Co zbývá

Jen nevratný @--blank@, který vymaže plaintextové e-maily a adresy u ověřených řádků. Pouští se vědomě a zvlášť:

php bin/crypto-seal-customers.php --blank --admin 24 --password 
php bin/crypto-seal-orders.php    --blank --admin 24 --password 
php bin/crypto-seal-outgoing.php  --blank --admin 24 --password 

Do té doby leží u 12 zákazníků, 4 objednávek a 26 řádků fronty plaintext vedle šifry. Je to úmyslný mezistav, ve kterém je sealing ještě vratný.

h3. Soubory s tajnostmi, které obřad zanechal

  • @/home/f/noveknihy-dev-zachranny-klic-2026-11-05.txt@ (0600) -- záchranný klíč, vypsal se jednou a už nikdy
  • @/home/f/noveknihy-dev-admin-devkeys.txt@ (0600) -- heslo administrátora @devkeys@, 16 znaků

Heslo bylo vygenerováno @passgenurand@ do souboru, který skript po přečtení maže, takže ho nikdo neviděl; bez téhle záchrany by se do dev administrace nedostal nikdo. Oba soubory patří do správce hesel a pak smazat -- dokud leží na tom stroji, není to záloha.

h3. Pojistky doplněné do skriptu

Skript @/home/f/obrad-dev-klice.sh@ dostal po reálném incidentu tři opravy:

Heslo se čte z @/dev/tty@, případně ze souboru, který se po přečtení smaže (@shred -u@) ještě než se zkontroluje. Původní @read -rs@ ze stdin v neinteraktivním kontextu vracelo prázdno a skript hlásil "heslo je kratší než 8 znaků", i když uživatel nic nenapsal.

Pojistka proti druhému spuštění: když @role_keys@ není prázdná, skript odmítne pracovat. Druhý běh by totiž přes @init | tee "$RECOVERY_OUT"@ přepsal soubor se záchranným klíčem chybovým výpisem -- a to je jediná jeho kopie. @RoleKeys::createRecovery()@ se sám brání, riziko bylo výhradně v tom obalu.

@set -o pipefail@ kolem toho @tee@: bez něj byl návratový kód pipeline kódem @tee@, tedy vždy nula, takže @|| exit 1@ by po selhání @init@ nikdy nevystřelilo.

Také k dispozici: Atom PDF