Check the online version, I often update my slides.

Talk detail

Taky máte ve firmě heslo “Žádný deploye v pátek”?

Jenže kdy teda? Když středa je přeci menší čtvrtek a čtvrtek je malej pátek. Naše aplikace zpracovává v průměru desítky tisíc požadavků a bezpečnostních browserových reportů za vteřinu a my jsme ji tuhle upgradovali na PHP 8.0. V pátek. Třináctýho.

Jak to děláme a proč?

Video pozvánka

Date and event

June 3, 2022, PHP live 2022 (talk duration 40 minutes, 35 slides)

Slides

Každej den je pátek, dejte mi od deployování svátek

#1 V některých firmách je zakázáno nasazovat nové verze aplikací v pátek. Prý když se něco pokazí, tak aby se to nemuselo opravovat v sobotu. To zní docela pochopitelně, teda do té doby, než zjistíte, že takovej pátek je vlastně skoro každej den. Ve středu musíte k doktorovi sami nebo s dětma, takže žádnej deploy v úterý. Příští týden začíná dovolená, takže žádnej deploy celej tenhle tejden, pro jistotu.

Já si myslím, že nasazovat nový verze se můžou i v pátek, ostatně my jsme přecházeli na PHP 8.0 taky v pátek, dokonce třináctýho. Akorát je potřeba na to myslet trochu dopředu. V této přednášce o tom budu povídat (i psát) a je celkem jedno, jestli nasazujete novou verzi vaší aplikace, nebo jestli upgradujete na novou verzi PHP. Na fotce jsem já na dovolený. Nedeplojuju, ale pracuju na přednášce na akci, která se nakonec nekonala ¯\_(ツ)_/¯

"Deprecations and backwards compatibility breaks" v PHP 8.1

#2 O těch hlavních novinkách v PHP 8.0, 8.1 atd. se dočtete v každým blog postu, ale mě zajímají i ty menší, které se zmiňují až na konci, jestli vůbec, v části tak tohle už nefunguje. Jako třeba to, že PHP od verze 8.1 už standardně escapuje i jednoduché uvozovky.

htmlspecialchars($neco) v PHP 8.1.0 a před

#3 To se hodí pro ochranu proti Cross-Site Scriptingu (XSS), pokud nepoužíváte nějaký chytrý šablonovací systém, např. když se pro ohraničení hodnot atributů v HTML používají právě jednoduché uvozovky. Pokud byste jako ochranu proti XSS totiž použili htmlspecialchars() bez dalších extra parametrů jak je naznačeno, tak by to ve starších verzích PHP nemuselo stačit.

htmlspecialchars($neco, ENT_QUOTES)

#4 Ve verzích do PHP 8.1 je totiž potřeba přidat flag ENT_QUOTES, aby se na entity převedly i ty jednoduché uvozovky, na což se dá jednoduše zapomenout. Od PHP 8.1 už to není potřeba a to se vyplatí!

"libxml_disable_entity_loader() has been deprecated" v PHP 8.0

#5 Další podobný bezpečnostní problém, který byl vyřešen stylem “takhle vám to už nebude fungovat”, je útok XML External Entity Injection (XXE). Knihovna libxml pohání veškeré XML funkce a třídy v PHP a od osmičky je vyžadována knihovna libxml 2.9.0 a novější, která má defaultně vypnuté nahrávání externích entit, čímž se XXE vyřešilo tak nějak samo.

Pomocí LIBXML_NOENT (“no entities”) si to zase můžete zapnout a externí entity budou nahrazeny, ale tím si “povolíte” i to XXE (ale můžete použít vlastní loader a tam to nějak vyřešit). Už pro ten “insecure” stav alespoň musíte něco udělat, defaultně už aplikace není zranitelná. No a tohle se dozvíte dokonce až v migration guide. Do PHP 8.0 jste mohli používat i starší libxml, takže bylo potřeba ručně nahrávání externích entit vypnout pomocí libxml_disable_entity_loader().

XML External Entity

#6 Při takovém tom běžném parsování uvedeného XML dokumentu se entita &foo; nahradí za obsah souboru /dir/foo.ent. To umožní společné části více dokumentů uložit na jedno místo a pak je jen vkládat, to je skvělé‽

XML External Entity Injection (XXE)

#7 Hmm, ne tak úplně. Pokud by parser dostával ke zpracování uživatelem poskytnuté dokumenty, tak by útočník mohl podstrčit takový soubor, který entitu nahradí obsahem souboru např. /etc/passwd. Samozřejmě pokud by existoval, parser měl právo ho číst, v PHP nebyl nastaven open_basedir a nebo byl nastaven fakt mistrovsky. Takhle by vzniklo to, čemu říkáme XML External Entity Injection (XXE).

XML External Entity Injection (XXE) s .php souborem

#8 Z /etc/passwd se ale člověk dnes nedozví skoro nic zajímavého, je to jen důkaz, že je něco špatně, je to takový alert(1) čtení souborů. Naštěstí můžeme přečíst třeba konfiguraci aplikace, to bude větší zábava, pro útočníka určitě.

XML External Entity Injection (XXE) s .php souborem a filter wrapperem

#9 PHP kód v souboru s příponou .php se vkládá do bloků, které začínají <?php. A v XML existuje “node”, který se jmenuje Processing Instructions – ten začíná <?. Takže když se XML parser bude snažit parsovat nějaký .php soubor s PHP kódem, tak určitě na nějaké to <? narazí a může nastat několik problémů, které způsobí, že se to XML nakonec nezparsuje: třeba že se nepodaří najít koncovou značku ?>, protože ta se často na konci .php souboru vynechává, je tam zbytečná. Nebo se při zkráceném zápisu <?= nepodaří ani zpracovat ta samotná instrukce. Takže by bylo potřeba externě injektnout PHP soubor tak, aby to jakoby nebyl PHP soubor.

S tím pomůže samotné PHP, konkrétně wrapper filter, který je standardně dostupný. XML parser v PHP pomocí něj můžeme požádat, aby při otvírání souboru, jehož obsah nahradí entitu &foo;, ho nejdříve prohnal přes tento wrapper. To uděláme zapsáním cesty k souboru “jako URL”, přičemž pomocí “schéma” (php://) a “hostu” (filter) vybereme “filter wrapper” a pomocí “cesty” convert.base64-encode zvolíme kódování do Base64 při čtení i zápisu (který nás v tuto chvíli nezajímá, ale aspoň je to kratší na napsání; klidně bych mohl použít i jen read=convert.base64-encode) a pomocí resource= (musí být na konci URL) zvolíme soubor, jehož obsah chceme přefiltrovat. Na místo entity &foo; se pak dosadí do Base64 zakódovaný soubor a pak se teprve začně parsovat XML, ve kterém už ale žádné <?php nebudou. Porovnejte příklady bez filtru a s filtrem.

PHP < 8.0: libxml_disable_entity_loader(); simplexml_load_string(); PHP 8.0: jenom simplexml_load_string()

#10 Před PHP 8.0 bylo nutné zavolat funkci libxml_disable_entity_loader() pro vypnutí načítání externích entit (a potažmo XXE), viz příklad, ale na to se dalo zapomenout. Nebo jste si museli být jistí, že používáte libxml 2.9.0 a novější, ale v tomhle světe si být něčím jistý není jednoduché. Nebo jste v kódu mohli kontrolovat, jestli takovou knihovnu máte, ale na to je podobně jako na volání libxml_disable_entity_loader() možné jednoduše zapomenout.

Od PHP 8.0 je libxml_disable_entity_loader() označena jako zastaralá a dokonce PHP vyhodí i Deprecated hlášku. Ale hlavně, není už potřeba ji používat! PHP 8.0 vyžaduje minimálně libxml 2.9.0, bez ní nejde zkompilovat, ta má defaultně entity loader vypnutý, viz příklad. A když není potřeba ji používat, tak ani nemůžete zapomenout ji používat.

$ipv6 = false; var_dump(count($ipv6) > 0); PHP < 8.0: Warning, PHP 8.0: Fatal error

#11 Další problém, na který jsem narazil a který by vyřešil i upgrade na novější PHP bylo tohle. Pokud se povedlo přeložit hostname na IP adresu, tak se mělo pokračovat dále, akorát že chyběla kontrola návratové hodnoty false. Takže i když se to přeložit nepovedlo, tak se před PHP 8.0 pokračovalo dále. Protože count(false) se provedlo jako count([false]) a to je ve výsledku 1 a to je > 0. Před PHP 8.0 to vyhodí jen Warning, od PHP 8.0 výjimku TypeError a šmitec. A to se strict_typesbez.

Spoiler alert!!!

#12 Jasně, je chybou vývojáře zapomenout zkontrolovat všechny možné návratové hodnoty, ale kdo to má vědět, co všechno to vrací. A právě tady malinko předběhnu…

PHPStan level 7: Parameter #1 $value of function count expects array|Countable, array|false given.

#13 Ví to třeba PHPStan, nástroj na statickou analýzu, který hojně využívám, protože mi opravdu pomáhá. Ondrovi Mirtesovi, autorovi PHPStanu, posílám i nějaké drobné na 🍺 a ☕, za každou ušetřenou minutu při debugování nebo refaktorování. PHPStan tenhle konkrétní problém detekuje na úrovni 7, kterou se snažím mít všude. Dlouho na ní frčel můj web a to dokonce bez ignorování chyb (teď už je na úrovni 8) i report-uri.com (ignorujeme jen nedefinované proměnné v .php šablonách, které používá CodeIgniter framework, kterého se po částech úspěšně zbavujeme).

May 29, 2022 8:00 AM Total requests 11.73M

#14 Takže… na nový péhápka dříve nebo později chceme a vy taky chcete. No jo, jenže takto vypadá typická zátěž report-uri.com za posledních 7 dní (v červnu 2022). Sice tam jsou nějaký ty “peaky”, ale i mimo ně (v neděli ráno, sobotní noc v Americe) to znamená 11 milionů požadavků za hodinu, 3000 požadavků za vteřinu. Níž se už nedostaneme. Nemůžeme si dovolit to vypnout od půlnoci do pěti do rána.

⬆ Upgrade na PHP 8.1 ↗ CSP reporty

#15 report-uri.com zpracovává reporty z prohlížečů, např. Content Security Policy (CSP), Network Error Logging (NEL) a další, ale také reporty z poštovních serverů (DMARC, TLS-RPT). Detailněji jsem o tom mluvil v některých dřívějších přednáškách a pokud vás třeba CSP zatím úspěšně míjelo, tak se to brzo možná změní.

canhas.report Simulate an attacker

#16 Pomocí Content Security Policy (CSP) můžete označit, který JavaScript na stránce je “povolený”, a který browser nemá spustit, protože ho tam možná dostal nějaký útočník. Vyzkoušet si to, včetně reportování, můžete na canhas.report.

PCI DSS Requirements and Testing Procedures 6.4.3: A method is implemented to confirm that each script is authorized

#17 Zákeřný JavaScript do platebních stránek vkládá skupina nebo skupiny, které se shodně označují jako “Magecart”. Takový JavaScript pak vyzobne údaje o platební kartě přímo z formuláře, kam ho uživatel zadá. To se samozřejmě nelíbí firmám Visa, MasterCard a dalším, které spolu kamarádí ve sdružení Payment Card Industry (PCI), které spravuje standard DSS (Data Security Standard).

Ten stanovuje pravidla, kterými je potřeba se řídit, když chcete přijímat platby platebními kartami (a to i když samotné karty neuchováváte, ale jen používáte nějaký iframe, který poskytuje platební brána). V březnu 2022 vyšla nová verze PCI DSS v4.0 a ta říká, že všechny skripty, které se nahrávají do platebních stránek musí být nějakým způsobem autorizovány a musí být zajištěna jejich integrita. Do března 2025 je to jen “to nejlepší co můžete udělat”, od dubna 2025 už je to vyžadováno.

Examples: Sub-resource integrity (SRI), a CSP, proprietary script or tag-management systems

#18 Toho lze dosáhnout právě například pomocí Content Security Policy, technologie, která dokáže omezit zdroje, odkud se JavaScript může spouštět. CSP je další úroveň ochrany proti útokům Cross-Site Scripting (XSS), kam se “Magecart” útoky také dají zařadit. Další možnost je Subresource Integrity (SRI), která využívá atribut integrity značky script, jehož hodnotou je hash obsahu souboru, který se má stáhnout. Skript se spustí pouze tehdy, pokud hash staženého obsahu odpovídá hashi v atributu integrity. SRI se hodí převážně pro vkládání souborů z CDN.

Upgrade to PHP 8.1: 10 of 13 tasks, Nov 30, 2021 "I've switched my dev env today"

#19 Asi je dobré zmínit, že když jsem na projektu začal pracovat, tak nic z popisovaného u nás nešlo, žádné, ale opravdu vůbec žádné testy neexistovaly a dokonce ani nešly rozumně napsat. Na nové verze PHP se raději nepřecházelo. Ale nějak jsem začal: první test na pár řádků jsem napsal v prosinci 2017, o den později přidal ručně spouštěnou kontrolu pomocí PHPStanu a automaticky to všechno začal spouštět asi o rok později.

Takže jak teda upgradujeme na ty nový PHP teď? To co vypadá jako jednodenní práce (“na PHP 8.0 jsme přešli v pátek třináctého”), je ve skutečnosti na několik měsíců, samozřejmě s docela dlouhými přestávkami, ne non-stop, uff. Tohle je aktuální (červen 2022) úkol pro přechod na 8.1, všimněte si několika věcí: datum kdy byl založen (listopad 2021, necelý týden po vydání PHP 8.1), že má 13 podúkolů a že jsem své vývojové prostředí přepnul ten samý den.

Od té doby mi už půl roku běží aplikace na PHP 8.1, ačkoliv na produkci běží na starším, ale stále plně podporovaném PHP 8.0. A jak tak aplikaci programuju a procházím, tak postupně objevuju věci, který je potřeba před přechodem vyřešit, a který pak přidávám jako podúkoly do toho jednoho velkýho úkolu. Těch podúkolů naštěstí zas není tolik, vyžaduje to fakt velký úsilí nějakej problém najít, zvlášť v týhle fázi 😅

Při upgrade PHP většinou upgradujeme i operační systém serverů, teď bychom šli na Ubuntu 22.04 LTS. No, upgradujeme, prostě uděláme úplně nový server s novou verzí systému i s novým PHP, dáme ho do poolu všech serverů na pár minut a když to vypadá v pořádku, tak uděláme pár desítek nových serverů a ty staré z poolu odstraníme a vypneme. Servery spravuje Ansible, takže je to jednodušší, než upgrade řešit klasicky.

composer.json: "config": { "platform": { "php": "8.0" } }

#20 V composer.json mám pomocí config.platform.php nastavenou “cílovou” verzi PHP, aby se mi neinstalovaly balíčky nebo jejich aktualizace, které by ve finále na produkci nefungovaly.

[PHP 8.1] Making tests pass on both PHP 8.0 and 8.1

#21 Jedna z prvních věcí, podle čísla issue dokonce úplně první podúkol, který jsem udělal, byla to, že jsem začal spouštět testy na obou PHP verzích, té produkční i té budoucí. Na lokále mám jen PHP 8.1, testy na obou verzích spouštíme na GitHub Actions. Udělal jsem to hned poté, co GitHub přidal PHP 8.1, další možností je akce Setup PHP – někde ji taky používám, nabízí více verzí PHP.

Jobs na PHP 8.0 a 8.1

#22 Dobrovolně se přiznám, že jsem dříve psaní testů nesnášel a tak pokud jsem vyloženě nemusel, žádné jsem nepsal. Vždyť jsem to přece napsal tak, aby to fungovalo, ne? Proč ještě trávit čas psaním testů, které to jen potvrdí 😅 Postupem času jsem začal psát testy ke všemu, na co sáhnu. Někdy je píšu před, někdy po, někdy před i po. Díky testům jsem pochopil, že i když to napíšu aby to fungovalo, tak že jsem klidně mohl zapomenout ošetřit nějakou chybovou návratovou hodnotu, třeba nějaký ten false, že, který se vynoří jednou za uherský rok. Nebo že jsem zapomněl na támhleten ten use case. Testy mi taky značně pomáhají s refactoringem a jsem nejistý, když mám někam přesunovat něco, k čemu nejsou testy. To je většinou raději nejdřív napíšu. Když objevíme nějaký bug v kódu, který je pokryt testy, tak vždy zkoumám, proč na to test nepřišel a snažím se, aby na to příště přišel.

Pokrytí kódu testy máme teď cca 45 %, což ale neznamená, že je otestováno stejný počet use kejsů. Navíc máme různý testy:

  • “info” je jen výpis informací o prostředí
  • “file-patterns” testuje správné pojmenování souborů, protože je sice fajn, když napíšete test, ale když ho dáte do špatného adresáře, nemusí se automaticky spouštět
  • “lint” testuje syntaktickou správnost .php souborů
  • “lint-latte” testuje Latte šablony
  • “lint-neon” .neon konfigurační soubory
  • “php-cs” (code sniffer) zjišťuje, jestli je vše správně odsazeno, velikost písmen v názvech tříd a vůbec všechno, co se týká coding stylu
  • “phpstan” je statická analýza aplikace včetně hledání různých zakázaných volání (var_dump() na produkci fakt nechcete)
  • a konečně “tester” jsou unit testy, integrační testy a další

Na lokále si ještě navíc curlem “HTTP-pingnu” aplikaci, resp. její dvě části, které sice používají rozdílné frameworky (CodeIgniter a Nette), ale sdílejí konfiguraci a vůbec všechno co se sdílet dá. Ale občas se jakoby může stát, že se jakoby zapomene zaregistrovat nějaká třída v jednom z těch frameworků a to pak jakoby hodí HTTP 503 Internal Error třeba rovnou na titulce tý konkrétní části a to je pak jakoby trapas. Hypoteticky samozřejmě, jakoby se mi to nikdy nestalo.

Jobs tls, testssl, certmonitor, heartbeat

#23 Taky testujeme, jestli je aplikace dostupná pomocí protokolů, pomocí kterých má být dostupná (ale to už nejsou testy PHP kódu, proto to neběží na dvou verzích PHP). Jednou se nám totiž stalo, že se bez jakéhokoliv zásahu někde něco po-to a aplikace začala být dostupná jen pomocí TLS 1.3. Bohužel jsme si toho nevšimli, protože my i naši uživatelé používáme moderní browsery, které TLS 1.3 už nějakou dobu umí. Na pár dní ale přestalo fungovat zpracování reportů z poštovních serverů, protože SendGrid, který nám přílohy z příchozích e-mailů s reporty parsuje a pak posílá jako JSON nějakým webhookem, umí jen TLS 1.2. SendGrid je pak doposlal, ale tohle najít mi dalo pěkně zabrat.

Na mým webu si hlídám i kvalitu HTTPS pomocí testssl.sh (něco jako SSL Labs, ale lze to spouštět lokálně) i platnost certifikátů pomocí vlastního monitoringu (v databázi mám vystavený certifikáty a jen se podívám, jestli na těch webech opravdu jsou), ale o tom asi jindy. No a podle hesla “kdo bude hlídat hlídače” si úplně na konci pingnu online monitoring, který mi dá vědět, když jednou za den ten heartbeat neuslyší.

Používám UptimeRobot a našel jsem ho tak, že jsem do Googlu zadal “cheapest uptime monitoring” 😊 a ono to umí docela dobrý věci, jen v případě HTTPS certifikátů od Let's Encrypt to na problém upozorní asi jen 3 dny před expirací. Což je docela pozdě, protože… co když vás to upozorní v pátek odpoledne? Doporučuje se obnovovat certifikáty 30 dní před koncem, a tak nejen proto používám monitoring vlastní. Tyhle a pár dalších testů najdete i u mě na GitHubu, klidně si je zkopírujte.

Once upgraded: Post-upgrade cleanup, As of PHP 8.1.0 calling setAccessible method has no effect

#24 Dva z těch třinácti podúkolů upgrade na PHP 8.1 říkají co kde je potřeba uklidit po přechodu na novější verzi a co už není potřeba používat. Nebojím se udělat si i metodu něco jako Php::isPhp80() a používat ji ve chvíli, kdy je na různých verzích PHP potřeba volat různý kód. Použil jsem to např. když v PHP 8.0 funkce libxml_disable_entity_loader(), viz výše, začala házet Deprecated, ale v 7.4 ji bylo potřeba používat: if (!Php::isPhp80) { libxml_disable_entity_loader(); } No a právě podobný věci je potřeba taky uklidit, tak abych na to nezapomněl.

[PHP 8.1] Vendor patches before they release themselves: stripe/stripe-php#1213, sendgrid/sendgrid-php#1066, Azure/azure-storage-php#321

#25 Tradičně asi největším problémem jsou knihovny třetích stran a to konkrétně proto, že… no, hádejte, je to ten třetí z těch třech zbývajících neuzavřených podúkolů. Do Microsoftem velmi opomíjené knihovny Azure/azure-storage-php jsem aktuálně třetím největším přispěvatelem podle počtu commitů (s 26 commity to třetí místo nebyl žádnej velkej záhul), protože tu knihovnu používáme a já docela potřebuju, aby nám to na nových PHP vůbec fungovalo. Většina těch commitů jsou opravy bugů, na který jsme přišli ať už při snaze přejít na novější PHP, nebo prostě pouhým používáním.

Pull request #321 jsem vytvořil 1. prosince 2021 a aktuálně (červen 2022) stále není ani zkontrolován nikým ze správců té knihovny. Po mnoha a mnoha urgencích nám v roce 2020 slíbili čtvrletní releasy, ale tuhle kadenci nedodrželi ani jednou. Podporu pro PHP 8.0 (tohle, tohle a další) vydali až v říjnu 2021, 10 měsíců po vydání PHP 8.0. Jestli nemusíte, tak víte, co nemáte používat. Já se fakt snažil, dokonce jim nabídl, že se správou balíku pomůžu, ale nezabralo nic. Well, fuck it.

[PHP 8.1] Add missing ReturnTypeWillChange merged into stripe:master from spaze:patch-1 on Dec 6, 2021

#26 U jiných knihoven, které používáme, je to mnohem lepší. Sice někdy taky nejsou úplně ready v den vydání nové verze PHP, ale aspoň to řeší trochu rychleji. Když najdu nějakou nekompatibilitu s mým čerstvě nainstalovaným novotou vonícím péhápkem na mým devu, tak se nejdřív podívám, jestli už jim někdo ten problém nahlásil nebo dokonce jestli už existuje oprava. Pokud ano, tak si ho jen přidám do seznamu podúkolů, pokud ne, tak se to snažím opravit sám a poslat jim to jako patch. Protože něco si do těch podúkolů napsat přece musím. Často to jsou fakt i drobnosti, žádný velký změny. Ale proč čekat, až to udělá někdo jiný, když už jsem to stejně prozkoumal.

tomto případě šlo jen o přidání chybějících atributů ReturnTypeWillChange. To je nový a první vestavěný atribut v PHP 8.1, který je potřeba přidat, pokud z nějakého důvodu nejde přesně dodržet návratové typy, které nařizuje třeba interface. Je to jen dočasné řešení, v PHP 9 už ten atribut nebude fungovat a bude potřeba správné návratové typy přidat. Snad to do té doby ve Stripe někdo stihne, haló, slyšíte mě tam?

Update packages: 7 days ago, 20 days ago, May 1, Apr 23, Mar 21, Jan 7

#27 Nové verze knihoven se do našeho repozitáře dostávají pravidelně tak jednou za měsíc. Udělám si kafe, otevřu noviny… a podívám se co je novýho (composer outdated), stáhnu novinky (composer update) a přečtu changelogy (pomocí pluginu pyrech/composer-changelogs), spustím sadu testů (vlastní skripty pomocí composer test), případně něco někde trochu poladím a udělám pull request.

Celý adresář vendor máme v Gitu (a já na mým webu taky) a máme pro to dobrý důvod: nechceme mít composer na serverech. Cizí kód, který by si odněkud stahoval nějaký další kód kdy se mu zachce. To je samozřejmě trochu přehnané, ale chceme na servery dostávat pokud možno veškerý kód z jediného místa – z našeho repozitáře, v čase, který určím já manuálním spuštěním composer update. Nechceme, aby když se dělá deploy, se někde (na našich serverech nebo na CI/CD serverech) spustil composer a stáhl balíčky, protože kdyby vývojáři nějakého toho balíku ruplo v kebuli a vydal fakt zajímavou novou verzi, nebo kdyby mu někdo nějak unesl účet a vydal zajímavou novou verzi za něj, tak by se taková zajímavá verze mohla dostat až do naší aplikace a to možná i docela nepozorovaně.

Takovým útokům se říká “supply chain attacks” a v poslední době je o nich slyšet na můj vkus až příliš často. V PHP světě se to přihodilo naposled před necelým měsícem (květen 2022), kdy někdo vydal novou verzi forknuté knihovny phpass (originál phpass byl v pořádku) – tu od PHP 5.5 není už třeba používat protože máme password_hash() a password_verify(), takže phpass (a navíc ten fork) naštěstí moc lidí nepoužívalo. Upravená knihovna kradla přístupy do AWS.

Takže tohle chceme mít plně pod kontrolou, snažím se alespoň zběžně prohlédnout diffy a čtu changelogy, a ne aby se nějaká 1.2.patch verze nainstalovala automaticky při změně cojávím třeba favicony. To by samozřejmě šlo řešit nastavením konkrétních verzí napevno do composer.json, ale co kdyby někdo nějak pošolichal packagist.org nebo getcomposer.org? Šance je to fakt prťavá až skoro žádná, composeru i packagistu a jejich vývojářům plně důvěřuju (i proto, že už jsem jim něco hlásil), ale pokud se tomu všemu lze vyhnout a jedinou daní za to je větší repository a velký diffy při aktualizaci balíčků (a v případě instalace závislostí z Gitu nutnost nějak pořešit .git adresáře, aby se z toho nestaly submoduly), tak za mě cajk.

composer.json: "extra": { "patches-file": "patches/composer.patches.json" }

#28 Abych na devu mohl provozovat tu jedinou knihovnu, která zatím oficiálně nepodporuje PHP 8.1, tak si ji musím patchnout. S vendorem v Gitu to není zas takový problém, horší je na to nezapomenout a případně ty soubory znovu patchnout při update. Navíc se snažíme tvářit, že vendor v Gitu nemáme a všechno děláme plusmínus jak by se mělo. Takže na patchování používáme plugin cweagans/composer-patches. V composer.json se do extra.patches-file uvede název souboru, ve kterém jsou jednotlivé patche vypsány. Podobný plugin je vaimo/composer-patches, ale jaký je mezi nimi rozdíl nevím.

composer.patches.json: { "patches": { "microsoft/azure-storage-blob": { "Check nulls": "patches/azure-321.diff" }, "microsoft/azure-storage-table": { "No EOLs in Content-Type": "patches/azure-328.diff" } } }

#29 V tom souboru máme v patches aktuálně uvedeny konkrétně tyto dva patche pro balíky microsoft/azure-storage-blob a microsoft/azure-storage-table, aby to na PHP 8.1 neházelo Deprecated a aby to fungovalo s nejnovějším guzzlehttp/psr7, které opravuje nějakou bezpečnostní chybu. Ty .diff soubory si můžete vyrobit ručně nebo je stáhnout rovnou z GitHubu, pokud tam existuje co potřebujete. Do URL pull requestu přidejte na konec .patch, uložit a máte to.

S patchem uloženým přímo z browseru jsem měl často docela problém, patch to nechtělo přijmout, možná nějaký mezery nebo co, nevím, nepodařilo se mi to rozlousknout (ale teď když to tu píšu, tak mě něco napadlo: jestli se mi to stane příště, tak mi prosím poraďte, že mám na mým VM zkusit ověřit přístupový práva pro věci stažený z webu, už snad budu vědět). Za to se mi to podařilo obejít, prostě jsem to stáhl wgetem a pak to šlo (možná proto, že to bylo stahovaný přímo v tý VM?). S lokálními opravami nemusíme čekat, až se Microsoft uráčí a můžeme na PHP 8.1 přejít klidně příští pátek.

trim() needs strings (in PHP 8.1 it would throw deprecated)

#30 Patrně největší problém při postupném přechodu na PHP 8.1 nám dělá, resp. dělalo, to, že funkce jako trim(), strpos(), strtoupper() a další nově vyžadují string a nelze místo něj zadat null.

trim(null) na PHP 8.1: Deprecated, dříve: nic

#31 Teda jako lze, ale hodí to Deprecated.

trim(null) a PHPStan level 7: No errors!

#32 PHPStan tento problém na námi používané úrovni 7 nedetekuje.

trim(null) a PHPStan level 8: Parameter #1 $string of function trim expects string, string|null given

#33 Problém najde až na úrovni 8, která oproti levelu 7 obsahuje navíc vlastně jen právě kontrolu nullables Jenže osmička je celkem brutální pro již existující kód, takže při kontrole na této úrovni mi vypadlo pár set chyb 😅 Z nich jsem si ale ručně vybral ty, které se týkaly trim() apod. a opravil je a PHPStan vrátil zas na level 7. Do budoucna by bylo fajn zkusit použít baseline a nový kód psát už tak, aby kontrolou na úrovni 8 prošel bez chyb a ten starý kód dovyřešit postupně nikdy.

Tlačítko "Revert" na GitHubu v pull requestu

#34 I při tom všem co děláme se může stát, že dorazí starý japonský mistr Onoseto a bude potřeba něco vrátit zpět. GitHub na to má tlačítko “Revert”, které vytvoří nový pull request, který revertuje uvedené změny. Opravdu funguje, vyzkoušeno za vás. Naštěstí ale skoro všechny věci jsme zvládli spíš rychle opravit, než abychom ztráceli čas revertováním. Ale je dobrý tu možnost mít a vědět o ní.

Monday Tuesday "W"ednesday "T"hursday "F"riday, Brent Rambo 👍 approves

#35 Tak jo, to je všechno a teď alou deployovat, není na to lepšího dne než závěr pracovního týdne, kdy i kalendář vám naznačuje, jak to dopadne 😁 Ha ha jakoby vtip. Přednášku klidně šiřte mezi kolegy a kamarády a sledujte mě na Twitteru, nebo i jinde a přijďte se třeba dozvědět něco nového z bezpečnosti na školení (next date: September 12–15, 2022 remote). Dík a čau. (BTW, Brent “👍” Rambo teď pracuje ve Facebooku)

Michal Špaček

Michal Špaček

I build web applications and I'm into web application security. I like to speak about secure development. My mission is to teach web developers how to build secure and fast web applications and why.

Public trainings

Come to my public trainings, everybody's welcome:

PHP application security
(September 12–15, 2022 )

HTTPS for developers & admins
(September 19–20, 2022 )