PHP DevOps & Security pt. 2

Bulletproof your continuous integration process

Während wir im vorangegangenen Tutorial gelernt haben, wie man eine professionelle Continuous Deployment-Pipeline für PHP-Projekte mit Gitlab und Laravel Envoy aufsetzt, möchte ich hier ergänzend einen Weg aufzeigen, wie man sein PHP-Projekt schon vor dem Einbringen des Codes in die Codebasis einigermaßen sicher mit statischen Tools vor bekannten Sicherheitslücken und Bugs schützen kann.


Der im ersten Teil vorgestellte Codesniffer PHPCS dient dem Sicherstellen der gemeinsamen Codestandards bei der Arbeit mit mehreren Entwicklern an einem Projekt.

Hier wird der Standard PSR2 (https://www.php-fig.org/psr/psr-2/) verwendet, der im Allgemeinen zur Zeit für Laravel-Projekte verwendet wird.


PHPCS und das Korrekturwerkzeug PHPCBF lassen sich wie folgt herunterladen:


wget https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
wget https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar


Für PHPCS empfiehlt es sich, eine Konfigurationsdatei im XML-Format bereitzustellen, exemplarisch nehmen wir hierbei eine Standard-Konfiguration für ein Laravel-Projekt.


Datei phpcs.xml:

<?xml version="1.0"?>
<ruleset name="Laravel Standards">
<description>The Laravel Coding Standards</description>
<file>app</file>
<file>config</file>
<file>public</file>
<file>resources</file>
<file>routes</file>
<exclude-pattern>*/database/*</exclude-pattern>
<exclude-pattern>*/cache/*</exclude-pattern>
<exclude-pattern>*/*.js</exclude-pattern>
<exclude-pattern>*/*.css</exclude-pattern>
<exclude-pattern>*/*.xml</exclude-pattern>
<exclude-pattern>*/*.blade.php</exclude-pattern>
<exclude-pattern>*/autoload.php</exclude-pattern>
<exclude-pattern>*/storage/*</exclude-pattern>
<exclude-pattern>*/docs/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/migrations/*</exclude-pattern>
<arg name="report" value="summary"/>
<arg name="colors"/>
<arg value="p"/>
<ini name="memory_limit" value="128M"/>
<rule ref="PSR2"/>
</ruleset>


Nach dem Abfrühstücken des gemeinsamen Codestandards wollen wir die Sicherheit unserer Anwendung verstärken. Dazu dient hier eine Security-Stage mit den Tools enlightn/security-checker und der PHPCS-Erweiterung pheromone/phpcs-security-audit.


Zunächst installieren wir den Paketmanager Composer im Hauptverzeichnis der Anwendung (oder alternativ global):

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && php composer-setup.php && rm composer-setup.php && php composer.phar install


Anschließend integrieren wir das enlightn/security-checker in die Anwendung mit

php composer.phar require --dev enlightn/security-checker


Dieses Tool prüft alle installierten Pakete (mitsamt Abhängigkeiten) in der Datei composer.lock auf bekannte Sicherheitsprobleme und Verwundbarkeiten anhand der Security Advisories Datenbank unter https://github.com/FriendsOfPHP/security-advisories.


Es lässt sich ausführen mit

php vendor/bin/security-checker security:check /path/to/composer.lock


Nach (bzw. im Script später vor) den Dependencies untersuchen wir unseren eigenen Code auf Sicherheitsprobleme und Anfälligkeiten. Dies geschieht mit der Erweiterung phpcs-seccurity-audit, die wir in unserer Anwendung installieren mit

php composer.phar require --dev pheromone/phpcs-security-audit


Mit dem Befehl

php phpcs.phar --config-set installed_paths vendor/pheromone/phpcs-security-audit/Security

machen wir die Erweiterung für PHPCS bekannt, anschließend testen wir unsere Anwendung auf Herz und Nieren mit

php phpcs.phar --report=summary --extensions=php,inc,lib,module,info --standard=./vendor/pheromone/phpcs-security-audit/example_base_ruleset.xml app/


Wenn man hierbei feststellen möchte, welche Dateien und Codestellen genau betroffen sind, wird einfach der Parameter –report weggelassen oder auf den Standardwert full gesetzt. Mit dem Wert

--report=svn-blame lässt sich, nebenbei erwähnt, zuverlässig der Bug-König unter seinen Kollegen ermitteln, aber diese kleine Gehässigkeit sparen wir uns hier. Nun haben wir einen Securitylayer, der mit statischen Mitteln eine hinreichende Sicherheit gegen bekannte Verwundbarkeiten bietet.


Um eine weiterführende statische Code-Analyse zur Aufdeckung von unerreichbarem Code, undefinierten Variablen, Typos oder falschen Parametern durchzuführen, empfiehlt sich gerade für Laravel-Projekte oder Projekte, bei denen viele Objekte oder Methoden on the fly definiert werden (was etwa phan dazu ungeeignet macht, zumindest in meinem Fall), das verbreitete Analysetool PHPStan.


Eine Konfiguration zieht Stan automatisch über eine phpstan.neon-Datei im Hauptverzeichnis des Projektes. Als Level habe ich hierbei 5 definiert, was ich auch grundsätzlich empfehlen würde, um einerseits voranzukommen und andererseits eine hinreichende Code-Qualität zu gewährleisten.

Eine Übersicht über alle Levels in PHPStan findest du unter https://phpstan.org/user-guide/rule-levels. Leider gibt PHPStan im Gegensatz zu anderen Tools wie Phan offenbar keinen Wert für eine erfolgreiche Ausführung der Tests zurück, deshalb wird es hier später in die Gitlab-Deployment-Konfigurationsdatei integriert mit dem Parameter allow_failure: true. Das bedeutet, das Gitlab das Ergebnis der Ausführung zwar anzeigt und in die jeweilige Artefakt-Datei generiert, aber für das Deployment ansonsten weiter ignoriert.


PHPStan muss zunächst im Projekt mit Composer für die Entwicklungsumgebung installiert werden:

php composer.phar require --dev phpstan/phpstan


Datei phpstan.neon:

includes:
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
paths:
- app
- vendor
# level 8 is the highest level
level: 5
ignoreErrors:
- '#Unsafe usage of new static#'
excludePaths:
- vendor
checkMissingIterableValueType: false
reportUnmatchedIgnoredErrors: false



Um wie im ersten Teil einen eigenen Container zum Testen der Anwendung zu haben, müssen wir wieder ein Docker-Image bauen. Dazu erstellen wir eine Datei namens Dockerfile im Hauptverzeichnis der Anwendung.


Datei Dockerfile:

FROM php:8.0


RUN apt-get update
RUN apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev libmcrypt-dev libzip-dev
RUN apt-get clean
RUN pecl install mcrypt-1.0.4 && docker-php-ext-enable mcrypt
RUN docker-php-ext-install pdo_mysql zip
RUN curl --silent --show-error "https://getcomposer.org/installer" | php -- --install-dir=/usr/local/bin --filename=composer


Dieses Image bauen wir und fügen es in die Registry des Gitlab-Repository ein mittels


docker login registry.gitlab.com

docker build -t registry.gitlab.com/<username>/<mein-repo>.git .

docker push registry.gitlab.com/<username>/<mein-repo>.git


Zu guter letzt fügen wir das ganze noch in einer Datei gitlab-ci.yml im Hauptverzeichnis des Projektes zusammen und committen und pushen diese in das Repository:


git add .

git commit -m „bulletproof my repo“

git push


Datei gitlab-ci.yml:

image: registry.gitlab.com/<username>/<project-name>:latest

services:
- mysql:5.7

variables:
MYSQL_DATABASE: homestead
MYSQL_ROOT_PASSWORD: secret
DB_HOST: mysql
DB_USERNAME: root

stages:
- linting
- security
- static_tests


phpcs_linter_PSR2:
stage: linting
image: registry.gitlab.com/pipeline-components/php-codesniffer:latest
script:
- phpcs --config-set show_warnings 0
- phpcs -s -p --colors --extensions=php --standard=phpcs.xml

security:
stage: security
script:
- composer install
- php phpcs.phar --config-set installed_paths vendor/pheromone/phpcs-security-audit/Security
- php phpcs.phar --config-set show_warnings 0
- php phpcs.phar --report=summary --extensions=php,inc,lib,module,info --standard=./vendor/pheromone/phpcs-security-audit/example_base_ruleset.xml app/
- php vendor/bin/security-checker security:check composer.lock

static_tests:
stage: static_tests
script:
- composer install
- vendor/bin/phpstan analyse src --level 1 --error-format gitlab > phpstan.json
artifacts:
paths:
- phpstan.json
expire_in: 1 week
when: always
allow_failure: true