From d360da327067174ebe10340aac5172d3877016c0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:55:55 +0200 Subject: [PATCH] fix(databases): chown SSL certs before container replacement (#11352) --- app/Actions/Database/StartMariadb.php | 4 ++-- app/Actions/Database/StartMongodb.php | 4 ++-- app/Actions/Database/StartMysql.php | 4 ++-- app/Actions/Database/StartPostgresql.php | 4 ++-- .../Unit/DatabaseSslCredentialEscapingTest.php | 18 +++++++++++++----- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index c4f98cfdfe..2f030ae299 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -208,11 +208,11 @@ class StartMariadb $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; - $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; - $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; if ($this->database->enable_ssl) { $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml run --rm --no-deps --user root --entrypoint chown $container_name mysql:mysql /etc/mysql/certs/server.key /etc/mysql/certs/server.crt < /dev/null"; } + $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 0f9bba6797..097e19f7b2 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -257,11 +257,11 @@ class StartMongodb $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; - $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; - $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; if ($this->database->enable_ssl) { $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml run --rm --no-deps --user root --entrypoint chown $container_name mongodb:mongodb /etc/mongo/certs/server.pem < /dev/null"; } + $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 3eab004643..d21ee02fb1 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -209,11 +209,11 @@ class StartMysql $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; - $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; - $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; if ($this->database->enable_ssl) { $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml run --rm --no-deps --user root --entrypoint chown $container_name mysql:mysql /etc/mysql/certs/server.key /etc/mysql/certs/server.crt < /dev/null"; } + $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 21cb278fbb..f70e8f3cfd 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -219,11 +219,11 @@ class StartPostgresql $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; - $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; - $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; if ($this->database->enable_ssl) { $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml run --rm --no-deps --user root --entrypoint chown $container_name postgres:postgres /var/lib/postgresql/certs/server.key /var/lib/postgresql/certs/server.crt < /dev/null"; } + $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/tests/Unit/DatabaseSslCredentialEscapingTest.php b/tests/Unit/DatabaseSslCredentialEscapingTest.php index 8ddc10a048..7b2ecf927e 100644 --- a/tests/Unit/DatabaseSslCredentialEscapingTest.php +++ b/tests/Unit/DatabaseSslCredentialEscapingTest.php @@ -21,33 +21,41 @@ it('escapeshellarg wraps postgres_user in single quotes for chown command', func ->toContain('chown'); }); -it('sets postgres SSL file ownership before starting the database container', function () { +it('sets postgres SSL file ownership before replacing the database container', function () { $source = file_get_contents(__DIR__.'/../../app/Actions/Database/StartPostgresql.php'); $permissionCommandPosition = strpos($source, '--entrypoint chown'); + $stopCommandPosition = strpos($source, 'dockerStopCommand'); $startCommandPosition = strpos($source, 'docker-compose.yml up -d'); expect($permissionCommandPosition)->not->toBeFalse() + ->and($stopCommandPosition)->not->toBeFalse() ->and($startCommandPosition)->not->toBeFalse() + ->and($permissionCommandPosition)->toBeLessThan($stopCommandPosition) ->and($permissionCommandPosition)->toBeLessThan($startCommandPosition) + ->and($source)->toContain('/var/lib/postgresql/certs/server.crt < /dev/null') ->and($source)->toContain('postgres:postgres /var/lib/postgresql/certs/server.key') ->not->toContain('chown {$postgresUser}:{$postgresUser}'); }); -it('sets database SSL file ownership before startup', function (string $action, string $owner, string $keyPath) { +it('sets database SSL file ownership before replacing the container', function (string $action, string $owner, string $keyPath, string $lastCertificatePath) { $source = file_get_contents(__DIR__."/../../app/Actions/Database/{$action}.php"); $permissionCommandPosition = strpos($source, '--entrypoint chown'); + $stopCommandPosition = strpos($source, 'dockerStopCommand'); $startCommandPosition = strpos($source, 'docker-compose.yml up -d'); expect($permissionCommandPosition)->not->toBeFalse() + ->and($stopCommandPosition)->not->toBeFalse() ->and($startCommandPosition)->not->toBeFalse() + ->and($permissionCommandPosition)->toBeLessThan($stopCommandPosition) ->and($permissionCommandPosition)->toBeLessThan($startCommandPosition) + ->and($source)->toContain("{$lastCertificatePath} < /dev/null") ->and($source)->toContain("{$owner} {$keyPath}"); })->with([ - 'mysql' => ['StartMysql', 'mysql:mysql', '/etc/mysql/certs/server.key'], - 'mariadb' => ['StartMariadb', 'mysql:mysql', '/etc/mysql/certs/server.key'], - 'mongodb' => ['StartMongodb', 'mongodb:mongodb', '/etc/mongo/certs/server.pem'], + 'mysql' => ['StartMysql', 'mysql:mysql', '/etc/mysql/certs/server.key', '/etc/mysql/certs/server.crt'], + 'mariadb' => ['StartMariadb', 'mysql:mysql', '/etc/mysql/certs/server.key', '/etc/mysql/certs/server.crt'], + 'mongodb' => ['StartMongodb', 'mongodb:mongodb', '/etc/mongo/certs/server.pem', '/etc/mongo/certs/server.pem'], ]); it('advisory PoC postgres_user payload is contained by escapeshellarg in chown command', function () {