Dal repository Remi alle Software Collections: come ricostruire PHP su RHEL e derivate

Le quattro famiglie di pacchetti PHP su RHEL, CentOS, Rocky Linux e AlmaLinux, come riconoscere quella installata e come ricostruire un RPM di PHP con le patch di sicurezza: sorgente, spec, mock, test, repository firmato, moduli e collezioni.

Copertina con il logo php∞, la categoria Distribuzioni Linux, il titolo Remi e Software Collections e il numero 18

Su un server Red Hat Enterprise Linux, CentOS, Rocky Linux o AlmaLinux, “abbiamo PHP 7.4” può voler dire cose molto diverse. Il pacchetto può arrivare dalla distribuzione, dalle Software Collections di Red Hat, dal repository di Remi Collet con i pacchetti che sostituiscono quelli di sistema o dalle sue collezioni installate in parallelo. Finché tutto viene aggiornato la differenza si nota poco. Quando il ramo è fuori supporto ed esce una vulnerabilità, la differenza diventa decisiva.

In questo articolo vediamo come riconoscere la famiglia di pacchetti installata, perché una correzione va applicata proprio a quel pacchetto e come si ricostruisce un RPM di PHP con le patch di sicurezza: sorgente, modifica dello spec, compilazione pulita, test, repository e rilascio. Ci soffermiamo sulle particolarità delle Software Collections e dei moduli di dnf, che sono le fonti più comuni di sorprese.

Perché l’origine del pacchetto conta

Un pacchetto RPM di PHP è molto più del codice sorgente ufficiale compilato. Contiene le opzioni di compilazione scelte da chi lo mantiene, la suddivisione delle estensioni in sottopacchetti, i percorsi dei file di configurazione, le patch già applicate e i file di servizio per PHP-FPM. Due pacchetti della stessa versione, uno di Red Hat e uno di Remi, possono differire in tutti questi punti.

Per questo una correzione di sicurezza non si installa compilando PHP a mano dal sito ufficiale. Una compilazione manuale finisce in percorsi diversi, perde le estensioni separate, sfugge al database dei pacchetti e verrebbe sovrascritta dal primo aggiornamento. Il modo corretto è partire dal pacchetto sorgente da cui è nato quello installato, aggiungere la correzione e ricostruirlo: il risultato è identico all’originale, con in più la patch.

Le quattro famiglie di pacchetti

Quattro schede che confrontano le famiglie di pacchetti PHP su RHEL e derivate. Distribuzione, da AppStream o base: pacchetti php-cli e php-fpm, versione scelta dal flusso o dal modulo, binario in /usr/bin/php, servizio php-fpm, configurazione in /etc/php.ini. Software Collections di Red Hat per EL 6 e 7: pacchetti rh-php73-php, versione nel nome, file in /opt/rh/rh-php73/root, servizio rh-php73-php-fpm, configurazione in /etc/opt/rh/rh-php73. Remi con i pacchetti base che sostituiscono quelli di sistema: php-cli e php-fpm, versione scelta con il modulo php:remi-8.2, binario in /usr/bin/php, servizio php-fpm, configurazione in /etc/php.ini. Remi con le collezioni per più versioni in parallelo: pacchetti php74-php, versione nel nome, file in /opt/remi/php74/root, servizio php74-php-fpm, configurazione in /etc/opt/remi/php74
Stessa versione di PHP, quattro modi diversi di installarla.

I pacchetti della distribuzione

Su RHEL 7 e CentOS 7 il PHP di sistema è il 5.4. Su RHEL 8 e 9 le versioni alternative sono flussi del modulo php nel repository AppStream, mentre su RHEL 10 sono normali pacchetti RPM. Rocky Linux e AlmaLinux ricostruiscono gli stessi pacchetti. Versioni e scadenze sono nell’articolo su PHP su RHEL 8, 9 e 10.

Le Software Collections di Red Hat

Su RHEL 6 e 7 Red Hat distribuiva versioni più recenti di PHP come Software Collections: pacchetti con un prefisso nel nome, come rh-php73, installati sotto /opt/rh accanto al PHP di sistema. Il progetto CentOS le ridistribuiva per CentOS 7. Secondo il ciclo di vita delle Red Hat Software Collections, l’ultima versione, PHP 7.3, ha raggiunto la fine del supporto a giugno 2024. Da RHEL 8 in poi le collezioni per PHP non esistono più, sostituite dagli Application Streams.

Il repository di Remi

Remi Collet, che contribuisce a PHP e a Fedora, mantiene da anni il repository più usato per PHP sui sistemi della famiglia Red Hat. Offre due tipi di pacchetti. Quelli base hanno lo stesso nome dei pacchetti di sistema e li sostituiscono: su EL 7 si sceglieva la versione abilitando un repository come remi-php74, su EL 8, 9 e 10 si abilita un flusso del modulo, come php:remi-8.2. Le collezioni, come php74-php-fpm, si installano invece sotto /opt/remi e permettono di avere più versioni sullo stesso server, una per sito.

Le FAQ del repository spiegano che per alcuni rami fuori supporto Remi include nei build più recenti le correzioni di sicurezza che riesce a riportare. Se il tuo server è su EL 8, 9 o 10 e usa Remi, la prima verifica è quindi di essere all’ultimo pacchetto disponibile. Su EL 6 e 7 invece i repository di Remi sono chiusi e nessun aggiornamento arriverà più.

Riconoscere cosa è installato

Bastano pochi comandi per capire a quale famiglia appartiene il PHP del server. Il campo Release è il più eloquente: un suffisso come .el8.remi indica Remi, il prefisso rh-php nel nome le collezioni di Red Hat, php74-php quelle di Remi.

# Nome, versione, release e fornitore di ogni pacchetto PHP
rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}  %{VENDOR}\n' | grep php | sort

# Repository da cui è stato installato ogni pacchetto (EL 8, 9 e 10)
dnf list installed 'php*'

# Flusso del modulo php attivo (EL 8 e 9)
dnf module list --enabled php

# Collezioni installate in parallelo e relativi servizi FPM
ls -d /opt/rh/* /opt/remi/* 2>/dev/null
systemctl list-units --type=service | grep -i fpm

Il procedimento, passo per passo

Il lavoro di ricostruzione segue sempre la stessa sequenza, qualunque sia la famiglia. Cambiano i dettagli dello spec e la configurazione dell’ambiente di compilazione.

Diagramma in sei passaggi per ricostruire un pacchetto PHP con le patch: 1 sorgente, il pacchetto .src.rpm esatto in uso; 2 patch, correzioni adattate e numero di release più alto; 3 build con mock in un ambiente pulito e identico; 4 test delle estensioni, della suite e dell’applicazione reale; 5 firma GPG e repository locale; 6 rilascio con Ansible, un server alla volta. Una freccia tratteggiata rossa indica il rollback, con il pacchetto precedente sempre pronto. Una nota ricorda che il pacchetto ha stesso nome, stessi percorsi e stesse estensioni, e che per i pacchetti modulari serve un repository che dnf non filtri con l’opzione module_hotfixes
Dal sorgente al rilascio, con il ritorno indietro sempre possibile.

Ecco i comandi principali, con un esempio su un sistema EL 9 e un PHP 7.4 di Remi. I nomi dei file vanno adattati alla versione installata:

# 1. Scarica il pacchetto sorgente della versione installata
#    (richiede dnf-plugins-core e i repository dei sorgenti abilitati)
dnf download --source php-cli

# 2. Installa il sorgente nell'albero di rpmbuild ed estrai il codice
rpm -ivh php-7.4.*.src.rpm
rpmbuild -bp --nodeps ~/rpmbuild/SPECS/php.spec

# 3. Aggiungi la patch allo spec (riga PatchNNN, applicazione
#    nella sezione %prep, Release più alta) e crea il nuovo sorgente
rpmbuild -bs ~/rpmbuild/SPECS/php.spec

# 4. Compila in un ambiente pulito; per i pacchetti di Remi la
#    configurazione di mock deve includere EPEL e i suoi repository
mock -r rocky+epel-9-x86_64 --rebuild ~/rpmbuild/SRPMS/php-7.4.*.src.rpm

# 5. Firma i pacchetti (chiave indicata in %_gpg_name) e crea il repository
rpm --addsign /var/lib/mock/rocky+epel-9-x86_64/result/*.rpm
cp /var/lib/mock/rocky+epel-9-x86_64/result/*.rpm /srv/repo/php/el9/
createrepo_c /srv/repo/php/el9/

La parte che richiede esperienza è il passaggio 3. Le correzioni pubblicate dal progetto PHP sono scritte per i rami supportati, e il codice dei rami vecchi può essere molto diverso: ogni patch va letta, adattata e verificata, a volte riscritta. È qui che si concentra il lavoro di backport. Il changelog dello spec deve elencare ogni CVE corretta, così chiunque può verificarla con rpm -q --changelog.

Il numero di release

Il numero di release del pacchetto ricostruito va scelto con attenzione. Deve essere più alto di quello installato, altrimenti dnf non lo considera un aggiornamento. Allo stesso tempo deve restare più basso di un eventuale build successivo pubblicato da chi mantiene il pacchetto originale: se Remi o la distribuzione rilasciano una nuova versione, il server deve poterla ricevere senza conflitti. Per questo aggiungiamo un suffisso riconoscibile alla release esistente, invece di incrementare il primo numero. Il suffisso rende anche evidente, a chi legge l’elenco dei pacchetti installati, quale build contiene le correzioni aggiuntive. Quando il repository originale è chiuso, come per EL 7, il problema non si pone e il suffisso resta utile per gli audit.

Le particolarità delle collezioni

Gli spec delle Software Collections usano le macro %scl per aggiungere il prefisso ai nomi e spostare i file sotto /opt. Per compilarli l’ambiente di build deve contenere i pacchetti che definiscono quelle macro per la collezione specifica, e il pacchetto ricostruito deve mantenere esattamente lo stesso prefisso. Il servizio da riavviare dopo l’aggiornamento ha il nome della collezione, per esempio rh-php73-php-fpm o php74-php-fpm, e la configurazione resta in /etc/opt.

Le particolarità dei moduli

Su EL 8 e 9, quando un flusso del modulo php è attivo, dnf nasconde i pacchetti con lo stesso nome che arrivano da repository non modulari. È il cosiddetto filtro modulare, ed è la ragione per cui un pacchetto ricostruito e pubblicato in un repository normale sembra non esistere. Le soluzioni sono due: rigenerare i metadati del modulo nel proprio repository, oppure dichiarare il repository come contenitore di correzioni con l’opzione module_hotfixes prevista da dnf.

# /etc/yum.repos.d/php-backport.repo
[php-backport]
name=PHP con le patch di sicurezza
baseurl=https://repo.esempio.lan/php/el9/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-php-backport
# Disattiva il filtro modulare per questo repository
module_hotfixes=1
# Priorità più alta (numero più basso) rispetto agli altri repository
priority=10

I test prima del rilascio

Compilare il pacchetto è solo metà del lavoro. Prima di arrivare in produzione passa da un ambiente di prova con l’applicazione reale, dove verifichiamo almeno questi punti:

  • la suite di test di PHP, che lo spec può eseguire durante la build, senza nuovi fallimenti rispetto al pacchetto originale;
  • lo stesso elenco di estensioni caricate e la stessa configurazione prima e dopo l’aggiornamento;
  • la sintassi della configurazione di PHP-FPM, prima di riavviare il servizio;
  • i percorsi critici dell’applicazione: accesso, ricerca, carrello, pagamento, invio di email, attività pianificate.
# Estensioni caricate prima dell'aggiornamento
php -m | sort > /root/php-moduli-prima.txt

# Dopo l'aggiornamento: nessuna differenza attesa
php -m | sort | diff /root/php-moduli-prima.txt -

# Verifica della configurazione di PHP-FPM prima del riavvio
php-fpm -t

Rilascio e ritorno indietro

Il rilascio lo facciamo con Ansible, un server alla volta, partendo da quello meno critico. Il pacchetto precedente resta nel repository, così il ritorno indietro è un comando: dnf history undo sulla transazione appena eseguita, oppure dnf downgrade sui pacchetti PHP. Su EL 7 i comandi equivalenti sono quelli di yum. Per un intervento d’urgenza su una singola CVE il nostro obiettivo è di 24 ore dall’analisi al rilascio, e la reperibilità H24 copre anche questa fase.

Sui sistemi fuori supporto, come CentOS 7, la ricostruzione si fa in un ambiente di compilazione della stessa versione, con i pacchetti archiviati della distribuzione: i dettagli sono nell’articolo su CentOS 7 dopo la fine del supporto. Vale anche qui il limite di ogni backport: ricostruire PHP corregge PHP, mentre il resto del sistema resta com’è. Se vuoi capire come applichiamo questo procedimento ai tuoi server, trovi i dettagli nella sezione sugli interventi.

Riassumendo

Sui sistemi della famiglia Red Hat PHP arriva da quattro famiglie di pacchetti: la distribuzione, con AppStream e moduli; le Software Collections di Red Hat, chiuse con PHP 7.3 a giugno 2024; i pacchetti base di Remi, che sostituiscono quelli di sistema; le collezioni di Remi, installate in parallelo sotto /opt/remi. Una correzione di sicurezza va applicata al pacchetto della famiglia già installata, ricostruendolo dal suo sorgente con la patch adattata e una release più alta, compilandolo con mock in un ambiente pulito, provandolo con l’applicazione reale e pubblicandolo in un repository firmato. Sui sistemi con moduli serve l’opzione module_hotfixes o i metadati del modulo; sulle collezioni bisogna rispettare prefisso e percorsi. Il risultato è un aggiornamento normale per il server, con un ritorno indietro sempre pronto.

IL PROSSIMO PASSO

Mettiamo
in sicurezza.

Indicaci versione di PHP, sistema operativo e cosa ti preoccupa. Ti risponderemo con una valutazione dell’esposizione e una proposta di intervento.

Richiedi supporto

Ti porta al modulo di richiesta, in quattro passi.

PHP LEGACY · MANAGED SERVER S.R.L.info@phplegacy.it +39 02 5656 9681

Lunedì – venerdì · 09:30 – 19:30

Urgenza in corso? Reperibilità H24
+39 340 985 4556 ↗