mirror of
https://github.com/dyzulk/trustlab-api.git
synced 2026-01-26 13:22:05 +07:00
feat: implement OS-specific installer sync and CDN redirect enhancements
This commit is contained in:
@@ -19,15 +19,19 @@ class PublicCaController extends Controller
|
|||||||
$caTypes = ['root', 'intermediate_2048', 'intermediate_4096'];
|
$caTypes = ['root', 'intermediate_2048', 'intermediate_4096'];
|
||||||
|
|
||||||
$certificates = CaCertificate::whereIn('ca_type', $caTypes)
|
$certificates = CaCertificate::whereIn('ca_type', $caTypes)
|
||||||
->get(['common_name', 'ca_type', 'serial_number', 'valid_to', 'cert_content', 'cert_path'])
|
->get(['common_name', 'ca_type', 'serial_number', 'valid_to', 'cert_content', 'cert_path', 'der_path', 'bat_path', 'mac_path', 'linux_path', 'last_synced_at'])
|
||||||
->map(function ($cert) {
|
->map(function ($cert) {
|
||||||
return [
|
return [
|
||||||
'name' => $cert->common_name,
|
'name' => $cert->common_name,
|
||||||
'type' => $cert->ca_type,
|
'type' => $cert->ca_type,
|
||||||
'serial' => $cert->serial_number,
|
'serial' => $cert->serial_number,
|
||||||
'expires_at' => $cert->valid_to->toIso8601String(),
|
'expires_at' => $cert->valid_to->toIso8601String(),
|
||||||
|
'last_synced_at' => $cert->last_synced_at ? $cert->last_synced_at->toIso8601String() : null,
|
||||||
'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,
|
'der_cdn_url' => $cert->der_path ? Storage::disk('r2-public')->url($cert->der_path) : null,
|
||||||
|
'bat_cdn_url' => $cert->bat_path ? Storage::disk('r2-public')->url($cert->bat_path) : null,
|
||||||
|
'mac_cdn_url' => $cert->mac_path ? Storage::disk('r2-public')->url($cert->mac_path) : null,
|
||||||
|
'linux_cdn_url' => $cert->linux_path ? Storage::disk('r2-public')->url($cert->linux_path) : null,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,6 +92,11 @@ class PublicCaController extends Controller
|
|||||||
$cert = CaCertificate::where('serial_number', $serial)->firstOrFail();
|
$cert = CaCertificate::where('serial_number', $serial)->firstOrFail();
|
||||||
$cert->increment('download_count');
|
$cert->increment('download_count');
|
||||||
$cert->update(['last_downloaded_at' => now()]);
|
$cert->update(['last_downloaded_at' => now()]);
|
||||||
|
|
||||||
|
if ($cert->bat_path) {
|
||||||
|
return redirect()->away(Storage::disk('r2-public')->url($cert->bat_path));
|
||||||
|
}
|
||||||
|
|
||||||
$store = $cert->ca_type === 'root' ? 'Root' : 'CA';
|
$store = $cert->ca_type === 'root' ? 'Root' : 'CA';
|
||||||
$filename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $cert->common_name);
|
$filename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $cert->common_name);
|
||||||
|
|
||||||
@@ -121,6 +130,10 @@ class PublicCaController extends Controller
|
|||||||
$cert->increment('download_count');
|
$cert->increment('download_count');
|
||||||
$cert->update(['last_downloaded_at' => now()]);
|
$cert->update(['last_downloaded_at' => now()]);
|
||||||
|
|
||||||
|
if ($cert->mac_path) {
|
||||||
|
return redirect()->away(Storage::disk('r2-public')->url($cert->mac_path));
|
||||||
|
}
|
||||||
|
|
||||||
// Extract Base64 payload
|
// Extract Base64 payload
|
||||||
$pem = $cert->cert_content;
|
$pem = $cert->cert_content;
|
||||||
$lines = explode("\n", trim($pem));
|
$lines = explode("\n", trim($pem));
|
||||||
@@ -181,4 +194,26 @@ class PublicCaController extends Controller
|
|||||||
->header('Content-Type', 'application/x-apple-aspen-config')
|
->header('Content-Type', 'application/x-apple-aspen-config')
|
||||||
->header('Content-Disposition', 'attachment; filename="' . $name . '.mobileconfig"');
|
->header('Content-Disposition', 'attachment; filename="' . $name . '.mobileconfig"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download Linux Installer (.sh)
|
||||||
|
*/
|
||||||
|
public function downloadLinux($serial)
|
||||||
|
{
|
||||||
|
$cert = CaCertificate::where('serial_number', $serial)->firstOrFail();
|
||||||
|
$cert->increment('download_count');
|
||||||
|
$cert->update(['last_downloaded_at' => now()]);
|
||||||
|
|
||||||
|
if ($cert->linux_path) {
|
||||||
|
return redirect()->away(Storage::disk('r2-public')->url($cert->linux_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback or dynamic generation if needed (already in Service)
|
||||||
|
$sslService = app(\App\Services\OpenSslService::class);
|
||||||
|
$script = $sslService->generateLinuxInstaller($cert);
|
||||||
|
|
||||||
|
return response($script)
|
||||||
|
->header('Content-Type', 'application/x-sh')
|
||||||
|
->header('Content-Disposition', 'attachment; filename="install-' . Str::slug($cert->common_name) . '.sh"');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ class CaCertificate extends Model
|
|||||||
'valid_to',
|
'valid_to',
|
||||||
'cert_path',
|
'cert_path',
|
||||||
'der_path',
|
'der_path',
|
||||||
|
'bat_path',
|
||||||
|
'mac_path',
|
||||||
|
'linux_path',
|
||||||
'last_synced_at',
|
'last_synced_at',
|
||||||
'download_count',
|
'download_count',
|
||||||
'last_downloaded_at'
|
'last_downloaded_at'
|
||||||
|
|||||||
@@ -412,7 +412,125 @@ class OpenSslService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Upload CA certificate (public) to R2 CDN in both PEM and DER formats.
|
* Generate Windows Installer (.bat)
|
||||||
|
*/
|
||||||
|
public function generateWindowsInstaller(CaCertificate $cert): string
|
||||||
|
{
|
||||||
|
$cdnUrl = $this->getPublicKeyCdnUrl($cert, 'cer'); // Windows prefer .cer (DER or PEM)
|
||||||
|
// Note: certutil can import PEM/CRT as well. We'll use the .crt (PEM) from CDN.
|
||||||
|
$cdnUrl = $cert->cert_path ? Storage::disk('r2-public')->url($cert->cert_path) : url("/api/public/ca/{$cert->uuid}/download/pem");
|
||||||
|
|
||||||
|
return "@echo off\n" .
|
||||||
|
"echo TrustLab - Installing Certificate: {$cert->common_name}\n" .
|
||||||
|
"set \"TEMP_CERT=%TEMP%\\trustlab-ca-{$cert->uuid}.crt\"\n" .
|
||||||
|
"curl -sL \"{$cdnUrl}\" -o \"%TEMP_CERT%\"\n" .
|
||||||
|
"if %ERRORLEVEL% NEQ 0 (\n" .
|
||||||
|
" echo Error: Failed to download certificate.\n" .
|
||||||
|
" pause\n" .
|
||||||
|
" exit /b 1\n" .
|
||||||
|
")\n" .
|
||||||
|
"certutil -addstore -f \"Root\" \"%TEMP_CERT%\"\n" .
|
||||||
|
"del \"%TEMP_CERT%\"\n" .
|
||||||
|
"echo Installation Complete.\n" .
|
||||||
|
"pause";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate macOS Configuration Profile (.mobileconfig)
|
||||||
|
*/
|
||||||
|
public function generateMacInstaller(CaCertificate $cert): string
|
||||||
|
{
|
||||||
|
$certBase64 = base64_encode($cert->cert_content);
|
||||||
|
$payloadId = "com.trustlab.ca." . Str::slug($cert->common_name);
|
||||||
|
$uuid1 = Str::uuid()->toString();
|
||||||
|
$uuid2 = Str::uuid()->toString();
|
||||||
|
|
||||||
|
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
|
||||||
|
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" .
|
||||||
|
"<plist version=\"1.0\">\n" .
|
||||||
|
"<dict>\n" .
|
||||||
|
" <key>PayloadContent</key>\n" .
|
||||||
|
" <array>\n" .
|
||||||
|
" <dict>\n" .
|
||||||
|
" <key>PayloadCertificateFileName</key>\n" .
|
||||||
|
" <string>{$cert->common_name}.crt</string>\n" .
|
||||||
|
" <key>PayloadContent</key>\n" .
|
||||||
|
" <data>{$certBase64}</data>\n" .
|
||||||
|
" <key>PayloadDescription</key>\n" .
|
||||||
|
" <string>TrustLab CA Certificate</string>\n" .
|
||||||
|
" <key>PayloadDisplayName</key>\n" .
|
||||||
|
" <string>{$cert->common_name}</string>\n" .
|
||||||
|
" <key>PayloadIdentifier</key>\n" .
|
||||||
|
" <string>{$payloadId}.cert</string>\n" .
|
||||||
|
" <key>PayloadType</key>\n" .
|
||||||
|
" <string>com.apple.security.root</string>\n" .
|
||||||
|
" <key>PayloadUUID</key>\n" .
|
||||||
|
" <string>{$uuid2}</string>\n" .
|
||||||
|
" <key>PayloadVersion</key>\n" .
|
||||||
|
" <integer>1</integer>\n" .
|
||||||
|
" </dict>\n" .
|
||||||
|
" </array>\n" .
|
||||||
|
" <key>PayloadDescription</key>\n" .
|
||||||
|
" <string>TrustLab CA Root Installation</string>\n" .
|
||||||
|
" <key>PayloadDisplayName</key>\n" .
|
||||||
|
" <string>TrustLab CA: {$cert->common_name}</string>\n" .
|
||||||
|
" <key>PayloadIdentifier</key>\n" .
|
||||||
|
" <string>{$payloadId}</string>\n" .
|
||||||
|
" <key>PayloadRemovalDisallowed</key>\n" .
|
||||||
|
" <false/>\n" .
|
||||||
|
" <key>PayloadType</key>\n" .
|
||||||
|
" <string>Configuration</string>\n" .
|
||||||
|
" <key>PayloadUUID</key>\n" .
|
||||||
|
" <string>{$uuid1}</string>\n" .
|
||||||
|
" <key>PayloadVersion</key>\n" .
|
||||||
|
" <integer>1</integer>\n" .
|
||||||
|
"</dict>\n" .
|
||||||
|
"</plist>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Linux Installer (.sh)
|
||||||
|
*/
|
||||||
|
public function generateLinuxInstaller(CaCertificate $cert): string
|
||||||
|
{
|
||||||
|
$cdnUrl = $cert->cert_path ? Storage::disk('r2-public')->url($cert->cert_path) : url("/api/public/ca/{$cert->uuid}/download/pem");
|
||||||
|
$filename = Str::slug($cert->common_name) . ".crt";
|
||||||
|
|
||||||
|
return "#!/bin/bash\n" .
|
||||||
|
"echo \"TrustLab - Installing CA Certificate: {$cert->common_name}\"\n" .
|
||||||
|
"if [ \"\$EUID\" -ne 0 ]; then echo \"Please run as root (sudo)\"; exit 1; fi\n" .
|
||||||
|
"TEMP_CERT=\"/tmp/trustlab-{$cert->uuid}.crt\"\n" .
|
||||||
|
"curl -sL \"{$cdnUrl}\" -o \"\$TEMP_CERT\"\n" .
|
||||||
|
"if [ ! -f \"\$TEMP_CERT\" ]; then echo \"Failed to download cert\"; exit 1; fi\n\n" .
|
||||||
|
"# Ubuntu/Debian\n" .
|
||||||
|
"if [ -d /usr/local/share/ca-certificates ]; then\n" .
|
||||||
|
" cp \"\$TEMP_CERT\" \"/usr/local/share/ca-certificates/{$filename}\"\n" .
|
||||||
|
" update-ca-certificates\n" .
|
||||||
|
"# RHEL/CentOS/Fedora\n" .
|
||||||
|
"elif [ -d /etc/pki/ca-trust/source/anchors ]; then\n" .
|
||||||
|
" cp \"\$TEMP_CERT\" \"/etc/pki/ca-trust/source/anchors/{$filename}\"\n" .
|
||||||
|
" update-ca-trust extract\n" .
|
||||||
|
"# Arch Linux\n" .
|
||||||
|
"elif [ -d /etc/ca-certificates/trust-source/anchors ]; then\n" .
|
||||||
|
" cp \"\$TEMP_CERT\" \"/etc/ca-certificates/trust-source/anchors/{$filename}\"\n" .
|
||||||
|
" trust extract-compat\n" .
|
||||||
|
"else\n" .
|
||||||
|
" echo \"Unsupported Linux distribution for automatic install.\"\n" .
|
||||||
|
" echo \"Please manually install \$TEMP_CERT\"\n" .
|
||||||
|
" exit 1\n" .
|
||||||
|
"fi\n" .
|
||||||
|
"rm \"\$TEMP_CERT\"\n" .
|
||||||
|
"echo \"Installation Complete.\"\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPublicKeyCdnUrl(CaCertificate $cert, $ext)
|
||||||
|
{
|
||||||
|
$path = $ext === 'der' ? $cert->der_path : $cert->cert_path;
|
||||||
|
return $path ? Storage::disk('r2-public')->url($path) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload CA certificate (public) to R2 CDN in 5 formats.
|
||||||
*/
|
*/
|
||||||
public function uploadToCdn(CaCertificate $cert)
|
public function uploadToCdn(CaCertificate $cert)
|
||||||
{
|
{
|
||||||
@@ -420,6 +538,9 @@ class OpenSslService
|
|||||||
$baseFilename = 'ca/' . Str::slug($cert->common_name) . '-' . $cert->uuid;
|
$baseFilename = 'ca/' . Str::slug($cert->common_name) . '-' . $cert->uuid;
|
||||||
$pemFilename = $baseFilename . '.crt';
|
$pemFilename = $baseFilename . '.crt';
|
||||||
$derFilename = $baseFilename . '.der';
|
$derFilename = $baseFilename . '.der';
|
||||||
|
$batFilename = $baseFilename . '.bat';
|
||||||
|
$macFilename = $baseFilename . '.mobileconfig';
|
||||||
|
$linuxFilename = $baseFilename . '.sh';
|
||||||
|
|
||||||
// 1. Upload PEM (.crt)
|
// 1. Upload PEM (.crt)
|
||||||
Storage::disk('r2-public')->put($pemFilename, $cert->cert_content, [
|
Storage::disk('r2-public')->put($pemFilename, $cert->cert_content, [
|
||||||
@@ -442,9 +563,33 @@ class OpenSslService
|
|||||||
'ContentType' => 'application/x-x509-ca-cert'
|
'ContentType' => 'application/x-x509-ca-cert'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 3. Generate and Upload Windows Installer (.bat)
|
||||||
|
$batContent = $this->generateWindowsInstaller($cert);
|
||||||
|
Storage::disk('r2-public')->put($batFilename, $batContent, [
|
||||||
|
'visibility' => 'public',
|
||||||
|
'ContentType' => 'application/x-msdos-program'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 4. Generate and Upload macOS Profile (.mobileconfig)
|
||||||
|
$macContent = $this->generateMacInstaller($cert);
|
||||||
|
Storage::disk('r2-public')->put($macFilename, $macContent, [
|
||||||
|
'visibility' => 'public',
|
||||||
|
'ContentType' => 'application/x-apple-aspen-config'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 5. Generate and Upload Linux Script (.sh)
|
||||||
|
$linuxContent = $this->generateLinuxInstaller($cert);
|
||||||
|
Storage::disk('r2-public')->put($linuxFilename, $linuxContent, [
|
||||||
|
'visibility' => 'public',
|
||||||
|
'ContentType' => 'application/x-sh'
|
||||||
|
]);
|
||||||
|
|
||||||
$cert->update([
|
$cert->update([
|
||||||
'cert_path' => $pemFilename,
|
'cert_path' => $pemFilename,
|
||||||
'der_path' => $derFilename,
|
'der_path' => $derFilename,
|
||||||
|
'bat_path' => $batFilename,
|
||||||
|
'mac_path' => $macFilename,
|
||||||
|
'linux_path' => $linuxFilename,
|
||||||
'last_synced_at' => now()
|
'last_synced_at' => now()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ return new class extends Migration
|
|||||||
// CDN Integration Columns
|
// CDN Integration Columns
|
||||||
$table->string('cert_path')->nullable();
|
$table->string('cert_path')->nullable();
|
||||||
$table->string('der_path')->nullable();
|
$table->string('der_path')->nullable();
|
||||||
|
$table->string('bat_path')->nullable();
|
||||||
|
$table->string('mac_path')->nullable();
|
||||||
|
$table->string('linux_path')->nullable();
|
||||||
$table->timestamp('last_synced_at')->nullable();
|
$table->timestamp('last_synced_at')->nullable();
|
||||||
|
|
||||||
$table->string('common_name')->nullable();
|
$table->string('common_name')->nullable();
|
||||||
@@ -43,8 +46,17 @@ return new class extends Migration
|
|||||||
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'der_path')) {
|
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'der_path')) {
|
||||||
$table->string('der_path')->nullable()->after('cert_path');
|
$table->string('der_path')->nullable()->after('cert_path');
|
||||||
}
|
}
|
||||||
|
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'bat_path')) {
|
||||||
|
$table->string('bat_path')->nullable()->after('der_path');
|
||||||
|
}
|
||||||
|
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'mac_path')) {
|
||||||
|
$table->string('mac_path')->nullable()->after('bat_path');
|
||||||
|
}
|
||||||
|
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'linux_path')) {
|
||||||
|
$table->string('linux_path')->nullable()->after('mac_path');
|
||||||
|
}
|
||||||
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'last_synced_at')) {
|
if (!Schema::connection('mysql_ca')->hasColumn('ca_certificates', 'last_synced_at')) {
|
||||||
$table->timestamp('last_synced_at')->nullable()->after('der_path');
|
$table->timestamp('last_synced_at')->nullable()->after('linux_path');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Route::get('/public/ca-certificates', [PublicCaController::class, 'index']);
|
|||||||
Route::get('/public/ca-certificates/{serial}/download', [PublicCaController::class, 'download']);
|
Route::get('/public/ca-certificates/{serial}/download', [PublicCaController::class, 'download']);
|
||||||
Route::get('/public/ca-certificates/{serial}/download/windows', [PublicCaController::class, 'downloadWindows']);
|
Route::get('/public/ca-certificates/{serial}/download/windows', [PublicCaController::class, 'downloadWindows']);
|
||||||
Route::get('/public/ca-certificates/{serial}/download/mac', [PublicCaController::class, 'downloadMac']);
|
Route::get('/public/ca-certificates/{serial}/download/mac', [PublicCaController::class, 'downloadMac']);
|
||||||
|
Route::get('/public/ca-certificates/{serial}/download/linux', [PublicCaController::class, 'downloadLinux']);
|
||||||
Route::post('/public/inquiries', [\App\Http\Controllers\Api\InquiryController::class, 'store']);
|
Route::post('/public/inquiries', [\App\Http\Controllers\Api\InquiryController::class, 'store']);
|
||||||
Route::get('/public/legal-pages', [\App\Http\Controllers\Api\LegalPageController::class, 'index']);
|
Route::get('/public/legal-pages', [\App\Http\Controllers\Api\LegalPageController::class, 'index']);
|
||||||
Route::get('/public/legal-pages/{slug}', [\App\Http\Controllers\Api\LegalPageController::class, 'show']);
|
Route::get('/public/legal-pages/{slug}', [\App\Http\Controllers\Api\LegalPageController::class, 'show']);
|
||||||
|
|||||||
Reference in New Issue
Block a user