mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-14 16:52:45 -04:00
remote_process() stored the full shell command as plain text in activity_log and kept it for 60 days. Database starts store the compose file with the database passwords, imports store S3 keys, and the Cloudflare tunnel setup stores its token. - Store the command encrypted. The queue worker decrypts it right before the SSH call and still reads plain commands of tasks queued before the upgrade. - Remove the command when the task is finished: after a successful run, in failed() when no attempt is left, and for a stopped import. A failed attempt that the queue retries keeps it. - Do not write a command preview into the error log. - Migration: remove stored commands from activities that no longer run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
3.5 KiB
PHP
88 lines
3.5 KiB
PHP
<?php
|
|
|
|
use App\Actions\CoolifyTask\RunRemoteProcess;
|
|
use App\Enums\ProcessStatus;
|
|
use App\Jobs\CoolifyTask;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\PrivateKey;
|
|
use App\Models\Server;
|
|
use App\Models\Team;
|
|
use App\Support\RemoteProcessCommand;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Process;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Spatie\Activitylog\Models\Activity;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
config()->set('cache.default', 'array');
|
|
config()->set('constants.ssh.mux_enabled', false);
|
|
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',
|
|
]);
|
|
$this->secretCommand = "echo 'POSTGRES_PASSWORD=super-secret-value' > /tmp/compose.env";
|
|
});
|
|
|
|
it('stores the command of a remote process encrypted', function () {
|
|
Queue::fake();
|
|
|
|
$activity = remote_process([$this->secretCommand], $this->server);
|
|
|
|
$stored = Activity::query()->findOrFail($activity->id);
|
|
expect(json_encode($stored->properties))->not->toContain('super-secret-value')
|
|
->and(RemoteProcessCommand::read($stored))->toBe($this->secretCommand);
|
|
Queue::assertPushed(CoolifyTask::class);
|
|
});
|
|
|
|
it('still reads a plain command that was queued before the upgrade', function () {
|
|
$activity = activity()
|
|
->withProperties(['server_uuid' => $this->server->uuid, 'command' => 'echo legacy', 'type' => 'inline', 'status' => ProcessStatus::QUEUED->value])
|
|
->event('inline')
|
|
->log('[]');
|
|
|
|
expect(RemoteProcessCommand::read($activity))->toBe('echo legacy');
|
|
});
|
|
|
|
it('removes the command after a successful final run', function () {
|
|
Queue::fake();
|
|
Process::fake(fn () => Process::result(output: 'ok'));
|
|
$activity = remote_process([$this->secretCommand], $this->server);
|
|
|
|
(new CoolifyTask(Activity::query()->findOrFail($activity->id), ignore_errors: false, call_event_on_finish: null, call_event_data: null))->handle();
|
|
|
|
$stored = Activity::query()->findOrFail($activity->id);
|
|
expect($stored->getExtraProperty('command'))->toBeNull()
|
|
->and($stored->getExtraProperty('command_encrypted'))->toBeNull()
|
|
->and($stored->getExtraProperty('status'))->toBe(ProcessStatus::FINISHED->value);
|
|
});
|
|
|
|
it('keeps the command after one attempt so the queue can retry the task', function () {
|
|
Queue::fake();
|
|
Process::fake(fn () => Process::result(errorOutput: 'Connection reset', exitCode: 255));
|
|
$activity = remote_process([$this->secretCommand], $this->server);
|
|
|
|
$run = new RunRemoteProcess(activity: Activity::query()->findOrFail($activity->id));
|
|
expect(fn () => $run())->toThrow(RuntimeException::class);
|
|
|
|
expect(RemoteProcessCommand::read(Activity::query()->findOrFail($activity->id)))->toBe($this->secretCommand);
|
|
});
|
|
|
|
it('removes the command when the task fails permanently', function () {
|
|
Queue::fake();
|
|
$activity = remote_process([$this->secretCommand], $this->server);
|
|
|
|
(new CoolifyTask(Activity::query()->findOrFail($activity->id), ignore_errors: false, call_event_on_finish: null, call_event_data: null))
|
|
->failed(new RuntimeException('SSH connection failed'));
|
|
|
|
$stored = Activity::query()->findOrFail($activity->id);
|
|
expect($stored->getExtraProperty('command'))->toBeNull()
|
|
->and($stored->getExtraProperty('status'))->toBe(ProcessStatus::ERROR->value);
|
|
});
|