diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index f7bac49da..f44008085 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -210,11 +210,11 @@ class StartMariadb $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"; + } $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; - if ($this->database->enable_ssl) { - $this->commands[] = executeInDocker($this->database->uuid, 'chown mysql:mysql /etc/mysql/certs/server.crt /etc/mysql/certs/server.key'); - } return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); } diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 784bebffc..b31251ce2 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -259,10 +259,10 @@ class StartMongodb $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"; - $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; if ($this->database->enable_ssl) { - $this->commands[] = executeInDocker($this->database->uuid, 'chown mongodb:mongodb /etc/mongo/certs/server.pem'); + $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"; } + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 39fcd391c..16b8e1585 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -211,12 +211,10 @@ class StartMysql $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"; - $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; - if ($this->database->enable_ssl) { - $mysqlUser = escapeshellarg($this->database->mysql_user); - $this->commands[] = executeInDocker($this->database->uuid, "chown {$mysqlUser}:{$mysqlUser} /etc/mysql/certs/server.crt /etc/mysql/certs/server.key"); + $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"; } + $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 8963ed26f..3b0f820df 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -221,11 +221,10 @@ class StartPostgresql $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"; - $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; if ($this->database->enable_ssl) { - $postgresUser = escapeshellarg($this->database->postgres_user); - $this->commands[] = executeInDocker($this->database->uuid, "chown {$postgresUser}:{$postgresUser} /var/lib/postgresql/certs/server.key /var/lib/postgresql/certs/server.crt"); + $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"; } + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); diff --git a/tests/Unit/DatabaseSslCredentialEscapingTest.php b/tests/Unit/DatabaseSslCredentialEscapingTest.php index 31f0133a0..8ddc10a04 100644 --- a/tests/Unit/DatabaseSslCredentialEscapingTest.php +++ b/tests/Unit/DatabaseSslCredentialEscapingTest.php @@ -21,6 +21,35 @@ 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 () { + $source = file_get_contents(__DIR__.'/../../app/Actions/Database/StartPostgresql.php'); + + $permissionCommandPosition = strpos($source, '--entrypoint chown'); + $startCommandPosition = strpos($source, 'docker-compose.yml up -d'); + + expect($permissionCommandPosition)->not->toBeFalse() + ->and($startCommandPosition)->not->toBeFalse() + ->and($permissionCommandPosition)->toBeLessThan($startCommandPosition) + ->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) { + $source = file_get_contents(__DIR__."/../../app/Actions/Database/{$action}.php"); + + $permissionCommandPosition = strpos($source, '--entrypoint chown'); + $startCommandPosition = strpos($source, 'docker-compose.yml up -d'); + + expect($permissionCommandPosition)->not->toBeFalse() + ->and($startCommandPosition)->not->toBeFalse() + ->and($permissionCommandPosition)->toBeLessThan($startCommandPosition) + ->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'], +]); + it('advisory PoC postgres_user payload is contained by escapeshellarg in chown command', function () { // Simulates a legacy row that bypassed validation $maliciousUser = 'root; touch /tmp/pwned_rce; #';