mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(databases): apply custom configs on servers with a strict umask
A custom config file written with tee gets the server umask. With umask 027 or 077 the file is not readable by the container user, so MySQL and MariaDB ignored the whole config without an error. - Set mode 644 on the MySQL, MariaDB, PostgreSQL, and MongoDB custom config files after each write. - Close the multiplexed SSH connection when the server user, IP, or port changes, so the next command does not run as the old user. Closes #11600
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- Several dev instances can run from one checkout (see `./scripts/dev urls`). After you add a migration, run it on each running instance of that checkout, or those instances fail on the new code.
|
||||
- The checkout is shared with other sessions. When the full suite fails, rerun each failing file alone and compare with a clean `git archive HEAD` copy before you connect a failure to your change. The dev instance of the checkout runs its live files, so a live repro also runs the uncommitted edits of other sessions: run `git diff` on the code under test before you trust the result.
|
||||
- Run seeders and other artisan commands that write SSH keys in a dev container as `www-data` (`docker exec -u www-data`). Root-owned files in `storage/app/ssh` make every SSH call from the web process fail.
|
||||
- Files written on a server with `tee` get the server umask (027/077 on hardened hosts). Set the mode explicitly (`chmod 644`) for files that a non-root container user reads through a read-only bind mount.
|
||||
- `sshd` keeps the login shell of an open multiplexed SSH connection. Call `SshMultiplexingHelper::removeMuxFile()` before a live test of SSH or login-shell behavior.
|
||||
- SSH retries replay the full command batch. Only exit code 255 is an SSH transport failure; other exit codes come from the remote command, so do not retry them. Keep batches safe to run twice (for example `docker rm -f <name> || true` before `docker run --name <name>`).
|
||||
- Redirect browser test output to a file (`> /tmp/x.log 2>&1`); piping it (`| tail`) hangs because the Playwright server keeps the pipe open.
|
||||
|
||||
@@ -282,5 +282,7 @@ class StartMariadb
|
||||
$content = $this->database->mariadb_conf;
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||
// The container user must read the read-only mount; a strict server umask leaves the file unreadable.
|
||||
$this->commands[] = "chmod 644 $this->configuration_dir/{$filename}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,6 +372,8 @@ class StartMongodb
|
||||
$content = $this->database->mongo_conf;
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||
// The container user must read the read-only mount; a strict server umask leaves the file unreadable.
|
||||
$this->commands[] = "chmod 644 $this->configuration_dir/{$filename}";
|
||||
}
|
||||
|
||||
private function add_default_database()
|
||||
|
||||
@@ -294,5 +294,7 @@ class StartMysql
|
||||
$content = $this->database->mysql_conf;
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||
// The container user must read the read-only mount; a strict server umask leaves the file unreadable.
|
||||
$this->commands[] = "chmod 644 $this->configuration_dir/{$filename}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,5 +342,7 @@ class StartPostgresql
|
||||
}
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $config_file_path > /dev/null";
|
||||
// The container user must read the read-only mount; a strict server umask leaves the file unreadable.
|
||||
$this->commands[] = "chmod 644 $config_file_path";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Enums\ServerRole;
|
||||
use App\Events\ServerReachabilityChanged;
|
||||
use App\Helpers\SshMultiplexingHelper;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Jobs\CheckTraefikVersionForServerJob;
|
||||
@@ -175,6 +176,9 @@ class Server extends BaseModel
|
||||
static::saved(function ($server) {
|
||||
if ($server->wasChanged('private_key_id') || $server->privateKey?->isDirty()) {
|
||||
refresh_server_connection($server->privateKey);
|
||||
} elseif ($server->wasChanged(['user', 'ip', 'port'])) {
|
||||
// An open multiplexed connection keeps the old user and host until it is closed.
|
||||
SshMultiplexingHelper::removeMuxFile($server);
|
||||
}
|
||||
});
|
||||
static::created(function ($server) {
|
||||
|
||||
@@ -97,3 +97,23 @@ it('writes the MongoDB server.pem before the certificate owner change mounts it'
|
||||
expect($commands)->toContain('Writing 1 missing configuration file.')
|
||||
->and(strpos($commands, 'Writing 1 missing configuration file.'))->toBeLessThan(strpos($commands, '--entrypoint chown'));
|
||||
});
|
||||
|
||||
dataset('database-start-actions-with-custom-config-files', [
|
||||
'postgresql' => ['create_standalone_postgresql', StartPostgresql::class, 'postgres_conf', 'custom-postgres.conf'],
|
||||
'mysql' => ['create_standalone_mysql', StartMysql::class, 'mysql_conf', 'custom-config.cnf'],
|
||||
'mariadb' => ['create_standalone_mariadb', StartMariadb::class, 'mariadb_conf', 'custom-config.cnf'],
|
||||
'mongodb' => ['create_standalone_mongodb', StartMongodb::class, 'mongo_conf', 'mongod.conf'],
|
||||
]);
|
||||
|
||||
it('makes the custom config file readable by the container user before the container starts', function (string $createDatabase, string $startAction, string $column, string $file) {
|
||||
$database = $createDatabase($this->environment->id, $this->destination);
|
||||
$database->update([$column => "# custom\n"]);
|
||||
|
||||
$startAction::run($database->fresh(), new Activity);
|
||||
|
||||
$path = $database->workdir()."/{$file}";
|
||||
$commands = implode("\n", $this->executor->commands);
|
||||
expect($commands)->toContain("chmod 644 {$path}")
|
||||
->and(strpos($commands, "tee {$path}"))->toBeLessThan(strpos($commands, "chmod 644 {$path}"))
|
||||
->and(strpos($commands, "chmod 644 {$path}"))->toBeLessThan(strpos($commands, 'docker-compose.yml up -d'));
|
||||
})->with('database-start-actions-with-custom-config-files');
|
||||
|
||||
@@ -603,11 +603,11 @@ describe('provisioning', function () {
|
||||
|
||||
it('does not start runners on the Coolify host', function () {
|
||||
Queue::fake();
|
||||
Process::fake();
|
||||
$server = runnerTestServer($this->team);
|
||||
$server->update(['ip' => 'host.docker.internal']);
|
||||
runnerTestConfig($server, $this->githubApp);
|
||||
$execution = runnerTestExecution($this->githubApp);
|
||||
Process::fake();
|
||||
|
||||
(new ProvisionGithubRunnerJob($execution->id))->handle();
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Server::flushIdentityMap();
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
$team = Team::factory()->create();
|
||||
$privateKey = PrivateKey::factory()->create(['team_id' => $team->id]);
|
||||
$this->server = Server::factory()->create(['team_id' => $team->id, 'private_key_id' => $privateKey->id, 'user' => 'root']);
|
||||
Process::fake();
|
||||
});
|
||||
|
||||
function closesServerMux(Server $server): Closure
|
||||
{
|
||||
return fn ($process) => str_contains($process->command, 'ssh -O stop') && str_contains($process->command, "mux_{$server->uuid}");
|
||||
}
|
||||
|
||||
it('closes the multiplexed SSH connection when the connection details change', function (array $changes) {
|
||||
$this->server->update($changes);
|
||||
|
||||
Process::assertRan(closesServerMux($this->server));
|
||||
})->with([
|
||||
'user' => [['user' => 'deploy']],
|
||||
'ip' => [['ip' => '203.0.113.20']],
|
||||
'port' => [['port' => 2222]],
|
||||
]);
|
||||
|
||||
it('keeps the multiplexed SSH connection when other server fields change', function () {
|
||||
$this->server->update(['name' => 'renamed', 'description' => 'changed']);
|
||||
|
||||
Process::assertDidntRun(closesServerMux($this->server));
|
||||
});
|
||||
Reference in New Issue
Block a user