Refresh DDEV project and simplify security configuration (#4296)

* Refresh DDEV project and phpMyAdmin setup

* Simplify DDEV security config setup

* Handle PDO SQLSTATE exception codes

* Update phpMyAdmin image to use specific SHA256 digest
This commit is contained in:
Adam Daley authored and GitHub committed 2026-09-11 13:56:46 +01:00
1 parent e8593ef54c
commit 7fb8024c29
10 files changed
+40 -357

No files matched your search

@@ -1,7 +1,7 @@
name: phpmyadmin
repository: ddev/ddev-phpmyadmin
version: v1.0.0
install_date: "2025-08-24T12:09:35-07:00"
version: v1.0.3
install_date: "2026-09-11T12:34:11+01:00"
project_files:
- docker-compose.phpmyadmin.yaml
- docker-compose.phpmyadmin_norouter.yaml
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
## #ddev-generated: If you want to edit and own this file, remove this line.
## Description: Launch a browser with PhpMyAdmin
+4 -265
View File
@@ -1,12 +1,11 @@
name: fossbilling.dev
name: fossbilling
type: php
docroot: src
php_version: "8.3"
nodejs_version: "24"
webserver_type: nginx-fpm
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
additional_hostnames: ["fossbilling.dev"]
database:
type: mariadb
version: "11.8"
@@ -15,131 +14,17 @@ hooks:
- exec: .ddev/post-start.sh
upload_dirs:
- data/uploads
- ../node_modules
use_dns_when_possible: true
composer_version: "2"
web_environment:
- IS_DDEV=true
- APP_ENV=dev
corepack_enable: false
omit_containers: ["ddev-ssh-agent"]
# Key features of DDEV's config.yaml:
# name: <projectname> # Name of the project, automatically provides
# http://projectname.ddev.site and https://projectname.ddev.site
# If the name is omitted, the project will take the name of the enclosing directory,
# which is useful if you want to have a copy of the project side by side with this one.
# type: <projecttype> # asterios, backdrop, cakephp, codeigniter, craftcms, drupal, drupal6, drupal7, drupal8, drupal9, drupal10, drupal11, drupal12, generic, joomla, laravel, magento, magento2, php, shopware6, silverstripe, symfony, typo3, wordpress, wp-bedrock
# See https://docs.ddev.com/en/stable/users/quickstart/ for more
# information on the different project types
# docroot: <relative_path> # Relative path to the directory containing index.php.
# php_version: "8.4" # PHP version to use, "5.6" through "8.5"
# You can explicitly specify the webimage but this
# is not recommended, as the images are often closely tied to DDEV's' behavior,
# so this can break upgrades.
# webimage: <docker_image>
# It’s unusual to change this option, and we don’t recommend it without Docker experience and a good reason.
# Typically, this means additions to the existing web image using a .ddev/web-build/Dockerfile.*
# database:
# type: <dbtype> # mysql, mariadb, postgres
# version: <version> # database version, like "10.11" or "8.0"
# MariaDB versions can be 5.5-10.8, 10.11, 11.4, 11.8
# MySQL versions can be 5.5-8.0, 8.4
# PostgreSQL versions can be 9-18
# router_http_port: <port> # Port to be used for http (defaults to global configuration, usually 80)
# router_https_port: <port> # Port for https (defaults to global configuration, usually 443)
# xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart"
# Note that for most people the commands
# "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better,
# as leaving Xdebug enabled all the time is a big performance hit.
# xhgui_http_port: "8143"
# xhgui_https_port: "8142"
# The XHGui ports can be changed from the default 8143 and 8142
# Very rarely used
# host_xhgui_port: "8142"
# Can be used to change the host binding port of the XHGui
# application. Rarely used; only when port conflict and
# bind_all_ports is used (normally with router disabled)
# xhprof_mode: [prepend|xhgui|global]
# Default is "xhgui"
# webserver_type: nginx-fpm, apache-fpm, generic
# timezone: Europe/Berlin
# If timezone is unset, DDEV will attempt to derive it from the host system timezone
# using the $TZ environment variable or the /etc/localtime symlink.
# This is the timezone used in the containers and by PHP;
# it can be set to any valid timezone,
# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# For example Europe/Dublin or MST7MDT
# composer_root: <relative_path>
# Relative path to the Composer root directory from the project root. This is
# the directory which contains the composer.json and where all Composer related
# commands are executed.
# composer_version: "2"
# You can set it to "" or "2" (default) for Composer v2
# to use the latest major version available at the time your container is built.
# It is also possible to use each other Composer version channel. This includes:
# - 2.2 (latest Composer LTS version)
# - stable
# - preview
# - snapshot
# Alternatively, an explicit Composer version may be specified, for example "2.2.18".
# To reinstall Composer after the image was built, run "ddev utility rebuild".
# nodejs_version: "24"
# change from the default system Node.js version to any other version.
# See https://docs.ddev.com/en/stable/users/configuration/config/#nodejs_version for more information
# and https://www.npmjs.com/package/n#specifying-nodejs-versions for the full documentation.
# corepack_enable: false
# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm
# additional_hostnames:
# - somename
# - someothername
# would provide http and https URLs for "somename.ddev.site"
# and "someothername.ddev.site".
# additional_fqdns:
# - example.com
# - sub1.example.com
# would provide http and https URLs for "example.com" and "sub1.example.com"
# Please take care with this because it can cause great confusion.
# upload_dirs: "custom/upload/dir"
#
# upload_dirs:
# - custom/upload/dir
# - ../private
#
# would set the destination paths for ddev import-files to <docroot>/custom/upload/dir
# When Mutagen is enabled this path is bind-mounted so that all the files
# in the upload_dirs don't have to be synced into Mutagen.
# disable_upload_dirs_warning: false
# If true, turns off the normal warning that says
# "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set"
# ddev_version_constraint: ""
# Example:
# ddev_version_constraint: ">= 1.24.8"
# This will enforce that the running ddev version is within this constraint.
# See https://github.com/Masterminds/semver#checking-version-constraints for
# supported constraint formats
# working_dir:
# web: /var/www/html
# db: /home
@@ -147,153 +32,7 @@ corepack_enable: false
# These values specify the destination directory for ddev ssh and the
# directory in which commands passed into ddev exec are run.
# omit_containers: [db, ddev-ssh-agent]
# Currently only these containers are supported. Some containers can also be
# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit
# the "db" container, several standard features of DDEV that access the
# database container will be unusable. In the global configuration it is also
# possible to omit ddev-router, but not here.
# performance_mode: "global"
# DDEV offers performance optimization strategies to improve the filesystem
# performance depending on your host system. Should be configured globally.
#
# If set, will override the global config. Possible values are:
# - "global": uses the value from the global config.
# - "none": disables performance optimization for this project.
# - "mutagen": enables Mutagen for this project.
#
# See https://docs.ddev.com/en/stable/users/install/performance/#mutagen
# fail_on_hook_fail: False
# Decide whether 'ddev start' should be interrupted by a failing hook
# host_https_port: "59002"
# The host port binding for https can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.
# host_webserver_port: "59001"
# The host port binding for the ddev-webserver can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.
# host_db_port: "59002"
# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic
# unless explicitly specified.
# mailpit_http_port: "8025"
# mailpit_https_port: "8026"
# The Mailpit ports can be changed from the default 8025 and 8026
# host_mailpit_port: "8025"
# The mailpit port is not normally bound on the host at all, instead being routed
# through ddev-router, but it can be bound directly to localhost if specified here.
# webimage_extra_packages: ['php${DDEV_PHP_VERSION}-tidy', 'php${DDEV_PHP_VERSION}-yac']
# Extra Debian packages that are needed in the webimage can be added here
# dbimage_extra_packages: [netcat, telnet, sudo]
# Extra Debian packages that are needed in the dbimage can be added here
# use_dns_when_possible: true
# If the host has internet access and the domain configured can
# successfully be looked up, DNS will be used for hostname resolution
# instead of editing /etc/hosts
# Defaults to true
# project_tld: ddev.site
# The top-level domain used for project URLs
# The default "ddev.site" allows DNS lookup via a wildcard
# share_default_provider: ngrok
# The default share provider to use for "ddev share"
# Defaults to global configuration, usually "ngrok"
# Can be "ngrok" or "cloudflared" or the name of a custom provider from .ddev/share-providers/
# share_provider_args: --basic-auth username:pass1234
# Provide extra flags to the share provider script
# See https://docs.ddev.com/en/stable/users/configuration/config/#share_provider_args
# disable_settings_management: false
# If true, DDEV will not create CMS-specific settings files like
# Drupal's settings.php/settings.ddev.php or TYPO3's additional.php
# In this case the user must provide all such settings.
# You can inject environment variables into the web container with:
# web_environment:
# - SOMEENV=somevalue
# - SOMEOTHERENV=someothervalue
# no_project_mount: false
# (Experimental) If true, DDEV will not mount the project into the web container;
# the user is responsible for mounting it manually or via a script.
# This is to enable experimentation with alternate file mounting strategies.
# For advanced users only!
# bind_all_interfaces: false
# If true, host ports will be bound on all network interfaces,
# not the localhost interface only. This means that ports
# will be available on the local network if the host firewall
# allows it.
# default_container_timeout: 120
# The default time that DDEV waits for all containers to become ready can be increased from
# the default 120. This helps in importing huge databases, for example.
#web_extra_exposed_ports:
#- name: nodejs
# container_port: 3000
# http_port: 2999
# https_port: 3000
#- name: something
# container_port: 4000
# https_port: 4000
# http_port: 3999
# Allows a set of extra ports to be exposed via ddev-router
# Fill in all three fields even if you don’t intend to use the https_port!
# If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start.
#
# The port behavior on the ddev-webserver must be arranged separately, for example
# using web_extra_daemons.
# For example, with a web app on port 3000 inside the container, this config would
# expose that web app on https://<project>.ddev.site:9999 and http://<project>.ddev.site:9998
# web_extra_exposed_ports:
# - name: myapp
# container_port: 3000
# http_port: 9998
# https_port: 9999
#web_extra_daemons:
#- name: "http-1"
# command: "/var/www/html/node_modules/.bin/http-server -p 3000"
# directory: /var/www/html
#- name: "http-2"
# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000"
# directory: /var/www/html
# override_config: false
# By default, config.*.yaml files are *merged* into the configuration
# But this means that some things can't be overridden
# For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge
# and you can't erase existing hooks or all environment variables.
# However, with "override_config: true" in a particular config.*.yaml file,
# 'use_dns_when_possible: false' can override the existing values, and
# hooks:
# post-start: []
# or
# web_environment: []
# or
# additional_hostnames: []
# can have their intended affect. 'override_config' affects only behavior of the
# config.*.yaml file it exists in.
# Many DDEV commands can be extended to run tasks before or after the
# DDEV command is executed, for example "post-start", "post-import-db",
# "pre-composer", "post-composer"
# See https://docs.ddev.com/en/stable/users/extend/custom-commands/ for more
# information on the commands that can be extended and the tasks you can define
# for them. Example:
#hooks:
+2 -5
View File
@@ -22,9 +22,6 @@ services:
- UPLOAD_LIMIT=4000M
- HTTP_EXPOSE=8036:80
- HTTPS_EXPOSE=8037:80
healthcheck:
interval: 120s
timeout: 2s
retries: 1
depends_on:
- db
db:
condition: service_healthy
+8 -4
View File
@@ -1,16 +1,20 @@
# Deny direct access to sensitive project files and directories.
location = /config.php {
return 403;
deny all;
}
location ^~ /vendor/ {
return 403;
deny all;
}
location ~* ^/themes/[^/]+/config/ {
return 403;
deny all;
}
location ^~ /data/ {
return 403;
deny all;
}
location ~* (?:\.(?:twig|lock|old|ya?ml|htaccess|htpasswd)|~)$ {
deny all;
}
-67
View File
@@ -1,67 +0,0 @@
server {
listen 80;
listen 443 ssl;
sendfile off;
include /etc/nginx/mime.types;
# Set your domain name here
server_name fossbilling.dev;
# Document root
root /var/www/html/src;
# SSL Setup
ssl_certificate /etc/ssl/certs/master.crt;
ssl_certificate_key /etc/ssl/certs/master.key;
# Security Headers
#add_header Strict-Transport-Security "max-age=31536000;" always;
#add_header X-Content-Type-Options nosniff;
# Set the index
index index.php;
# Block access to sensitive files
location ~* \.(htaccess|htpasswd|bak|conf|inc|ini|lock|log|old|sh|sql|twig|ya?ml)$|(composer\.json|README\.md|CHANGELOG\.md)$ {
return 403;
}
# Block access to hidden files except .well-known
location ~ /\.(?!well-known\/) {
return 403;
}
# Block access to theme configuration files
location ~* ^/themes/[^/]+/config/ {
return 403;
}
# Deny access to runtime data.
location ^~ /data/ {
return 403;
}
# Routing
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP Handling
location ~ ^/(index\.php|ipn\.php|install/index\.php|install/install\.php)$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# On severs with multiple PHP versions the syntax the socket would likely be similar to "unix:/run/php/php8.4-fpm.sock;"
fastcgi_pass unix:/run/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_intercept_errors on;
}
# Block PHP files
location ~* \.php$ {
return 403;
}
}
+2
View File
@@ -0,0 +1,2 @@
[PHP]
disable_functions = exec,passthru,system,shell_exec,popen,proc_open
@@ -1,12 +0,0 @@
#!/bin/bash
set -eu
PHP_VERSION="$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')"
FPM_CONF_DIR="/etc/php/${PHP_VERSION}/fpm/conf.d"
SECURITY_INI="${FPM_CONF_DIR}/99-fossbilling-security.ini"
cat > "${SECURITY_INI}" <<'EOF'
[PHP]
disable_functions = exec,passthru,system,shell_exec,popen,proc_open
EOF
@@ -41,7 +41,10 @@ final readonly class ExceptionResponseFactory
return new Response($this->renderWhoops($exception), $this->getStatusCode($exception));
}
return new Response((new ErrorPage())->renderPage($exception->getCode(), $message), $this->getStatusCode($exception));
// PDO exceptions can carry a string SQLSTATE instead of an application error code.
$code = $exception->getCode();
return new Response((new ErrorPage())->renderPage(is_int($code) ? $code : 0, $message), $this->getStatusCode($exception));
}
public function formatTestingMessage(\Throwable $exception): string
@@ -50,3 +50,20 @@ test('error page can render without sending output', function (): void {
->and($page)->toContain('Error Code: #404')
->and($page)->toContain('Missing route');
});
test('exception response factory renders string SQLSTATE errors outside testing', function (): void {
$previousEnv = getenv('APP_ENV');
putenv('APP_ENV=prod');
$exception = new PDOException('Unknown column');
(new ReflectionProperty($exception, 'code'))->setValue($exception, '42S22');
try {
$response = (new ExceptionResponseFactory())->create($exception);
} finally {
putenv($previousEnv === false ? 'APP_ENV' : 'APP_ENV=' . $previousEnv);
}
expect($response->getStatusCode())->toBe(Response::HTTP_INTERNAL_SERVER_ERROR)
->and($response->getContent())->toContain('FOSSBilling Error')
->and($response->getContent())->toContain('Unknown column');
});