feat: unify ca_certificates migrations (clean slate)

This commit is contained in:
dyzulk
2026-01-06 14:29:50 +07:00
parent ce0b646077
commit 759e60670f
5 changed files with 51 additions and 35 deletions

View File

@@ -27,6 +27,7 @@ class PublicCaController extends Controller
'serial' => $cert->serial_number, 'serial' => $cert->serial_number,
'expires_at' => $cert->valid_to->toIso8601String(), 'expires_at' => $cert->valid_to->toIso8601String(),
'cdn_url' => $cert->cert_path ? Storage::disk('r2-public')->url($cert->cert_path) : null, 'cdn_url' => $cert->cert_path ? Storage::disk('r2-public')->url($cert->cert_path) : null,
'der_cdn_url' => $cert->der_path ? Storage::disk('r2-public')->url($cert->der_path) : null,
]; ];
}); });
@@ -52,6 +53,11 @@ class PublicCaController extends Controller
} }
if ($format === 'der') { if ($format === 'der') {
// Redirect to CDN if path exists and format is DER
if ($cert->der_path) {
return redirect()->away(Storage::disk('r2-public')->url($cert->der_path));
}
// Convert PEM to DER (Base64 decode the body) // Convert PEM to DER (Base64 decode the body)
$pem = $cert->cert_content; $pem = $cert->cert_content;
$lines = explode("\n", trim($pem)); $lines = explode("\n", trim($pem));

View File

@@ -24,6 +24,7 @@ class CaCertificate extends Model
'valid_from', 'valid_from',
'valid_to', 'valid_to',
'cert_path', 'cert_path',
'der_path',
'last_synced_at', 'last_synced_at',
'download_count', 'download_count',
'last_downloaded_at' 'last_downloaded_at'

View File

@@ -412,20 +412,39 @@ class OpenSslService
} }
} }
/** /**
* Upload CA certificate (public) to R2 CDN. * Upload CA certificate (public) to R2 CDN in both PEM and DER formats.
*/ */
public function uploadToCdn(CaCertificate $cert) public function uploadToCdn(CaCertificate $cert)
{ {
try { try {
$filename = 'ca/' . Str::slug($cert->common_name) . '-' . $cert->uuid . '.crt'; $baseFilename = 'ca/' . Str::slug($cert->common_name) . '-' . $cert->uuid;
$pemFilename = $baseFilename . '.crt';
$derFilename = $baseFilename . '.der';
Storage::disk('r2-public')->put($filename, $cert->cert_content, [ // 1. Upload PEM (.crt)
Storage::disk('r2-public')->put($pemFilename, $cert->cert_content, [
'visibility' => 'public',
'ContentType' => 'application/x-x509-ca-cert'
]);
// 2. Convert to DER and Upload (.der)
$lines = explode("\n", trim($cert->cert_content));
$payload = '';
foreach ($lines as $line) {
if (!str_starts_with($line, '-----')) {
$payload .= trim($line);
}
}
$derContent = base64_decode($payload);
Storage::disk('r2-public')->put($derFilename, $derContent, [
'visibility' => 'public', 'visibility' => 'public',
'ContentType' => 'application/x-x509-ca-cert' 'ContentType' => 'application/x-x509-ca-cert'
]); ]);
$cert->update([ $cert->update([
'cert_path' => $filename, 'cert_path' => $pemFilename,
'der_path' => $derFilename,
'last_synced_at' => now() 'last_synced_at' => now()
]); ]);

View File

@@ -11,23 +11,42 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
// 1. Create table if not exists (Clean Slate for fresh install)
if (!Schema::connection('mysql_ca')->hasTable('ca_certificates')) { if (!Schema::connection('mysql_ca')->hasTable('ca_certificates')) {
Schema::connection('mysql_ca')->create('ca_certificates', function (Blueprint $table) { Schema::connection('mysql_ca')->create('ca_certificates', function (Blueprint $table) {
$table->string('uuid', 32)->primary(); $table->string('uuid', 32)->primary();
$table->string('ca_type'); // root, intermediate_4096, intermediate_2048 $table->string('ca_type');
$table->longText('cert_content')->nullable(); $table->longText('cert_content')->nullable();
$table->longText('key_content')->nullable(); $table->longText('key_content')->nullable();
$table->string('serial_number')->nullable(); $table->string('serial_number')->nullable();
// CDN Integration Columns
$table->string('cert_path')->nullable();
$table->string('der_path')->nullable();
$table->timestamp('last_synced_at')->nullable();
$table->string('common_name')->nullable(); $table->string('common_name')->nullable();
$table->string('organization')->nullable(); $table->string('organization')->nullable();
$table->dateTime('valid_from')->nullable(); $table->dateTime('valid_from')->nullable();
$table->dateTime('valid_to')->nullable(); $table->dateTime('valid_to')->nullable();
// Tracking
$table->unsignedBigInteger('download_count')->default(0); $table->unsignedBigInteger('download_count')->default(0);
$table->timestamp('last_downloaded_at')->nullable(); $table->timestamp('last_downloaded_at')->nullable();
$table->timestamps(); $table->timestamps();
}); });
} else {
// 2. Self-Healing: Add missing columns if table already exists (Production Sync)
Schema::connection('mysql_ca')->table('ca_certificates', function (Blueprint $table) {
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'cert_path')) {
$table->string('cert_path')->nullable()->after('serial_number');
}
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'der_path')) {
$table->string('der_path')->nullable()->after('cert_path');
}
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'last_synced_at')) {
$table->timestamp('last_synced_at')->nullable()->after('der_path');
}
});
} }
} }

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::connection('mysql_ca')->table('ca_certificates', function (Blueprint $table) {
$table->string('cert_path')->nullable()->after('serial_number');
$table->timestamp('last_synced_at')->nullable()->after('cert_path');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::connection('mysql_ca')->table('ca_certificates', function (Blueprint $table) {
$table->dropColumn(['cert_path', 'last_synced_at']);
});
}
};