mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(security): encrypt stored remote commands and remove them after the final attempt
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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
aa61214233
commit
f540c49f68
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
/**
|
||||
* The command of a remote_process() activity.
|
||||
*
|
||||
* The queue worker reads the command from the activity, so it must be stored until the
|
||||
* task is finished. Commands can contain secrets (database passwords, S3 keys, tunnel
|
||||
* tokens), so the command is stored encrypted and removed after the final attempt.
|
||||
*/
|
||||
class RemoteProcessCommand
|
||||
{
|
||||
public const PROPERTY = 'command';
|
||||
|
||||
public const ENCRYPTED_PROPERTY = 'command_encrypted';
|
||||
|
||||
/**
|
||||
* @return array{command: string, command_encrypted: true}
|
||||
*/
|
||||
public static function properties(string $command): array
|
||||
{
|
||||
return [
|
||||
self::PROPERTY => Crypt::encryptString($command),
|
||||
self::ENCRYPTED_PROPERTY => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The plain command, or null when it was already removed.
|
||||
* Activities queued before encryption was added store the command as plain text.
|
||||
*/
|
||||
public static function read(Activity $activity): ?string
|
||||
{
|
||||
$command = $activity->getExtraProperty(self::PROPERTY);
|
||||
if (! is_string($command)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $activity->getExtraProperty(self::ENCRYPTED_PROPERTY) === true
|
||||
? Crypt::decryptString($command)
|
||||
: $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the command after the final attempt of the task.
|
||||
* Reads the stored row first, so a status that Coolify set meanwhile is kept.
|
||||
*/
|
||||
public static function forget(Activity $activity): void
|
||||
{
|
||||
$stored = Activity::query()->whereKey($activity->getKey())->first();
|
||||
if ($stored === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stored->properties = $stored->properties->except([self::PROPERTY, self::ENCRYPTED_PROPERTY]);
|
||||
$stored->save();
|
||||
|
||||
$activity->properties = $stored->properties;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user