Files
portal-api/app/Services/VirtualMachine/Rebuild/VmRebuildPasswordService.php
T

127 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\VirtualMachine\Rebuild;
use App\DTOs\VirtualMachine\Rebuild\RebuildContext;
use App\Http\Facade\SignRSA;
use App\Models\ModuleVmware;
use App\Models\ReinstallLog;
use App\Models\vCenter as VCenterModel;
use App\Services\VsphereHostSshService;
use Illuminate\Support\Facades\Log;
use RuntimeException;
/**
* VmRebuildPasswordService
*
* Generates a new random password for the rebuilt VM and pushes it through
* VsphereHostSshService::changePassword(). Uses the same low-level SSH helper
* as the create flow but never dispatches ChangePasswordViaVsphereJob.
*
* The new credential is persisted on the EXISTING ModuleVmware row — the
* service identity is preserved across rebuild.
*/
final class VmRebuildPasswordService
{
public function __construct(private readonly VsphereHostSshService $ssh)
{
}
/**
* @param int|null $reinstallLogId If supplied, the customer-supplied
* password (if any) is pulled from this
* log row and the same row is updated
* with the credential that was applied.
* @return string the plaintext password actually applied
*/
public function rotate(RebuildContext $ctx, ?int $reinstallLogId = null): string
{
if ($ctx->newVmId === null) {
throw new RuntimeException('Rebuild password: newVmId is null');
}
$vcenter = VCenterModel::find($ctx->vcenterId);
if (!$vcenter) {
throw new RuntimeException("Rebuild password: vCenter #{$ctx->vcenterId} not found");
}
// Prefer a customer-supplied credential when one was provided.
$newPassword = null;
$log = $reinstallLogId ? ReinstallLog::find($reinstallLogId) : null;
if ($log && $log->password_supplied_by_customer && $log->password) {
try {
$newPassword = SignRSA::DecryptFunc($log->password);
} catch (\Throwable $e) {
$newPassword = null;
}
}
if (!is_string($newPassword) || $newPassword === '') {
$newPassword = $this->generatePassword(16);
}
$output = $this->ssh->changePassword(
$ctx->newVmId,
$newPassword,
$ctx->osFamily,
(string) $vcenter->host,
(string) $vcenter->user,
SignRSA::DecryptFunc($vcenter->password),
);
if (!str_contains(strtoupper(trim($output)), 'SUCCESS')) {
throw new RuntimeException(
"Rebuild password: changePassword script failed — " . trim($output),
);
}
$encrypted = SignRSA::EncryptFunc($newPassword);
ModuleVmware::where('id', $ctx->moduleVmwareId)->update([
'password' => $encrypted,
'plain_password' => $newPassword,
]);
if ($log) {
$log->update([
'password' => $encrypted,
'plain_password' => $newPassword,
]);
}
Log::info('VmRebuildPasswordService: password rotated', [
'module_vmware_id' => $ctx->moduleVmwareId,
'new_vm_id' => $ctx->newVmId,
'reinstall_log_id' => $log?->id,
]);
return $newPassword;
}
private function generatePassword(int $length): string
{
$sets = [
'ABCDEFGHJKLMNPQRSTUVWXYZ',
'abcdefghjkmnpqrstuvwxyz',
'23456789',
'!@#$*',
];
$password = '';
$allChars = implode('', $sets);
foreach ($sets as $set) {
$password .= $set[random_int(0, strlen($set) - 1)];
}
for ($i = count($sets); $i < $length; $i++) {
$password .= $allChars[random_int(0, strlen($allChars) - 1)];
}
$chars = str_split($password);
for ($i = count($chars) - 1; $i > 0; $i--) {
$j = random_int(0, $i);
[$chars[$i], $chars[$j]] = [$chars[$j], $chars[$i]];
}
return implode('', $chars);
}
}