Files
coolify/app/Jobs/RegenerateSslCertJob.php
T
Andras Bacsai a2cd6351b2 fix(databases): keep SSL certificate files and CA in sync with each engine
- Regenerate SSL Certificates now writes server.crt/server.key for every
  engine except MongoDB, which keeps the combined server.pem. Before, the
  button always wrote server.pem, so Postgres, MySQL, MariaDB, Redis, KeyDB
  and Dragonfly failed to start after a regenerate.
- The certificate renewal job uses the same rule, so MongoDB no longer
  switches to server.crt/server.key on renewal and fails on restart.
- Starting an SSL database writes the CA that signs its certificate to
  /data/coolify/ssl/coolify-ca.crt, so a stale CA file (for example one
  written by another server entry for the same host) no longer causes
  "certificate signature failure".
- Saving a custom CA certificate that does not match the stored CA
  private key is rejected.
- The CA file write commands live in SslHelper::caCertificateFileCommands().

Fixes #10388
Fixes #6771
Fixes #11926
2026-09-30 19:01:11 +02:00

84 lines
2.7 KiB
PHP

<?php
namespace App\Jobs;
use App\Helpers\SslHelper;
use App\Models\SslCertificate;
use App\Models\Team;
use App\Notifications\SslExpirationNotification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class RegenerateSslCertJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $backoff = 60;
public function __construct(
protected ?Team $team = null,
protected ?int $server_id = null,
protected bool $force_regeneration = false,
) {}
public function handle()
{
$query = SslCertificate::query();
if ($this->server_id) {
$query->where('server_id', $this->server_id);
}
if (! $this->force_regeneration) {
$query->where('valid_until', '<=', now()->addDays(14));
}
$query->where('is_ca_certificate', false);
$regenerated = collect();
$query->cursor()->each(function ($certificate) use ($regenerated) {
try {
$caCert = $certificate->server->sslCertificates()
->where('is_ca_certificate', true)
->first();
if (! $caCert) {
Log::error("No CA certificate found for server_id: {$certificate->server_id}");
return;
}
SslHelper::generateSslCertificate(
commonName: $certificate->common_name,
subjectAlternativeNames: $certificate->subject_alternative_names,
resourceType: $certificate->resource_type,
resourceId: $certificate->resource_id,
serverId: $certificate->server_id,
configurationDir: $certificate->configuration_dir,
mountPath: $certificate->mount_path,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
isPemKeyFileRequired: $certificate->requiresPemKeyFile(),
);
$resource = $certificate->database;
if ($resource) {
$regenerated->push($resource);
}
} catch (\Exception $e) {
Log::error('Failed to regenerate SSL certificate: '.$e->getMessage());
}
});
if ($regenerated->isNotEmpty()) {
$this->team?->notify(new SslExpirationNotification($regenerated));
}
}
}