{ Megoldva ] Apache + PHP-FPM kérelemkeveredés

Tesztkörnyezetet alakítottam ki két Apache VirtualHost-tal, amihez két külön PHP-FPM pool tartozik. Mindkét pool külön user-re van chroot-olva.

Az egyik VirtualHost a domain.tld és aliasként a www.domain.tld kérelmeket szolgálja ki az egyik php pool-al.

A másik VirtualHost az aldomain.domain.tld kérelmeket szolgálja ki a másik php pool-lal.

Amennyiben egy kérelem úgy érkezik be az aldomain.domain.tld alá, hogy közben ugyanarra a php fájlra a domain.tld alá is érkezett kérelem, úgy az aldomain-ra érkező kérelmet a domain alatt lévő php kezdi el kiszolgálni. Vagyis:

Ha a http://domain.tld/index.php és http://aldomain.domain.tld/index.php kérelmek egyszerre érkeznek be az Apache-hoz, úgy a http://aldomain.domain.tld/index.php kérelem kiszolgálását a domain.tld/index.php kezdi el, de időközben átveszi azt az aldomain.domain.tld pool-ja, és természetesen hibát dob. Ha a kérelem kiszolgálása gyorsabban befejeződik, mint ahogy átvenné akkor hiba nélkül befejeződik a kérelem kiszolgálása.

A két pool két külön user-re van chroot-olva, tehát még véletlenül sem lenne szabad egymás tárterületeit elérniük. Azonban az aldomain.domain.tld alatti php hibanaplóban megjelenik a hibaüzenet, ami egy domain.tld alatti php fájlban jelez hibát, amint egy másik, domain.tld alatti php-t próbál include paranccsal behívni. A hiba szerint az include-ban szereplő fájl nem elérhető. De az a php fájl, amiben ez a hibás include van, maga is egy include-dal került végrehajtásra, és arra még nem jelzett hibát, pedig mindkét fájl a chroot-on kívüli másik user fájlrendszerében érhető csak el.

A keveredés csak akkor fordul elő, ha az aldomain.domain.tld/index.php kérelem egyszerre kerül kiszolgálásra a domain.tld/index.php kérelemmel.

A keveredés akkor is előfordul, ha a domain.tld/index.php fájl csak egy "print time();" parancsot tartalmaz, és még session-t sem indít (az auto_session is off). Ez esetben hibaüzenet ugyan nem keletkezik, mivel túl gyorsan befejeződik a kérelem kiszolgálása, de az aldomain.domain.tld/index.php kérelemre megjelenik az időbélyeg, pedig a print time() parancs csak a másik user tárterületén tárolt index.php fájlban van.

A hiba előfordulását nem befolyásolja, hogy az url-ben szerepel-e az index.php vagy a defaultIndex miatt indul el. A lényeg, hogy mindkét VirtualHost-on belül ugyanolyan nevű php szolgálja ki a kérelmet. Akkor is jelentkezik, ha mindkettő az index2.php-ra vonatkozik.

A két VirtualHost ServerName és ServerAlias értékei csak konkrét domainnevek, és nincs átfedés közöttük, kivéve, hogy minden aldomain a domain.tld aldomainje. Emiatt azt várnám, hogy a PHP pool-ok már az Apache szintjén elkülönülnek, ami általában igaz is, kivéve, ha egyszerre érkeznek be a kérelmek.

A hiba nem fordul elő, ha

  • a két kérelem mindegyike aldomain, azaz a domain.tld helyett a www.domain.tld-re érkezik a párhuzamos kérelem. Sajnos sikerült közben ilyen esetben is reprodukálnom a jelenséget.
  • akkor sem fordul elő, ha a két egyszerre beérkező kérelem nem ugyanarra a php-ra vontkozik, vagyis az egyik mondjuk index.php-ra, a másik index2.php-ra.

Erre varrjatok gombot.

Ha valakinek van ötletet, hogy kerülhető meg ez a hiba, vagy hogyan lehet úgy konfigurálni akár az Apache, akár a PHP-FMP rendszert, hogy még ebben a speciális esetben se keveredhessenek össze a kérelmek, kérem ossza meg velem.

Megoldás:

opcache.validate_root = 1

Hozzászólások

bemasolnad a beallitasaidat?

neked aztan fura humorod van...

latni kellene a vhost beallitasokat es az fpm configokat

Kérdés, hogy az FPM opciók kellenek-e egyáltalán? Hisz az apache-nak már biztosítania kellene, az elkülönítést, amit jól is csinál, kivéve, ha egyszerre érkeznek be a kérelmek. Ha az FPM lenne rosszul konfigurálva, akkor minden kérelemnél hibás lenne.

Az Apache konfig fontos részei:

<VirtualHost *:80>
    ServerName domain.tld
    ServerAlias www.domain.tld
    DocumentRoot "/home/domain.tld/public"
    SetEnv document_root "/public"
    SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
    <FilesMatch "\.php$">
        <If "-f %{REQUEST_FILENAME}">
            SetHandler "proxy:unix:/run/php/domain.tld-8.2.sock|fcgi://localhost"
        </If>
    </FilesMatch>
    <Directory /home/domain.tld/public>
        Require all granted
    </Directory>
    CustomLog /home/domain.tld/log/domain.tld-access.log combined
    ErrorLog /home/domain.tld/log/domain.tld-error.log
</VirtualHost>

Az aldomain.domain.tld is analog:

<VirtualHost *:80>
    ServerName aldomain.domain.tld
    ServerAlias aldomain2.domain.tld
    DocumentRoot "/home/aldomain.domain.tld/public"
    SetEnv document_root "/public"
    SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
    <FilesMatch "\.php$">
        <If "-f %{REQUEST_FILENAME}">
            SetHandler "proxy:unix:/run/php/aldomain.domain.tld-8.2.sock|fcgi://localhost"
        </If>
    </FilesMatch>
    <Directory /home/aldomain.domain.tld/public>
        Require all granted
    </Directory>
    CustomLog /home/aldomain.domain.tld/log/domain.tld-access.log combined
    ErrorLog /home/aldomain.domain.tld/log/domain.tld-error.log
</VirtualHost>

A két user a domain.tld és az aldomain.tld.

Csúf megoláds, de működhet: a domain.tld-t ne szolgáld ki, helyette legyen egy redirect a www.domain.tld-re. Innen kezdve működik a dolog: azt írtad, hogy aldomainek kiszolgálása esetén a probléma nem áll fenn.

Tudom, a hiba oka is érdekelne - azt sajnos nem tudom, de engem is érdekelne.

https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.validate-root

eloszor nem hittem a szememnek, le is fordittattam a translate-el is.

"Megakadályozza a névütközéseket chrootolt környezetekben. Ezt minden chrootolt környezetben engedélyezni kell, hogy megakadályozza a chrooton kívüli fájlokhoz való hozzáférést."

a chroot mire valo? :)

neked aztan fura humorod van...

:) igen, ez tenyleg csoda hogy mukodik.

en csak arra reagaltam, hogy a parameter beallitasa kell ahhoz, hogy "megakadályozza a chrooton kívüli fájlokhoz való hozzáférést". ertem en, hogy cache-eli es igy a masik chroot-bol szarmazo fajlt hasznalja, mert chroot-on belul ugyanaz az utvonal, de nem a fajlhoz fer hozza, hanem a cache-elt fajlhoz, ami persze ugyanugy problema.

amugy nincs valami azonosito, amit az utvonal ele lehetne biggyeszteni, mielot hash-eli az utvonalat? mondjuk a socket inode-jat, amin kommunikal.

pl. a /var/lib/php8.2-fpm/web1.sock es web2.sock inode-ja, ezek nem lesznek ugyanazok:

hash('1000:/app.php')

hash('1001:/app.php')

neked aztan fura humorod van...