127 lines
4.1 KiB
PHP
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);
|
|
}
|
|
}
|