Files
portal-api/app/Services/VmProvisionService.php
T

145 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services;
use App\Http\Facade\SignRSA;
use App\Http\Services\vCenter as VCenterService;
use App\Models\ModuleVmware;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
final class VmProvisionService
{
public const OS_LINUX = 'LINUX';
public const OS_WINDOWS = 'WINDOWS';
/** Default password baked into every VM template */
public static function templatePassword(): string
{
return config('vmware.template_password');
}
// -------------------------------------------------------------------------
// vCenter REST helpers (used by WaitVmReadyJob if still in flow)
// -------------------------------------------------------------------------
public function loginVCenter(\App\Models\vCenter $vcenter, VCenterService $service): void
{
if (!$service->loginServer($vcenter)) {
throw new \RuntimeException("vCenter login failed for host={$vcenter->host}");
}
}
/**
* @return array{tools_running:bool,ip_address:string|null,os_family:string|null}
*/
public function getVmGuestInfo(VCenterService $service, string $vmId): array
{
try {
$response = Http::withoutVerifying()
->withHeaders(['vmware-api-session-id' => $service->token])
->timeout(10)
->get("https://{$service->apiUri}/rest/vcenter/vm/{$vmId}/guest/identity");
if (!$response->successful()) {
return ['tools_running' => false, 'ip_address' => null, 'os_family' => null];
}
$value = $response->json('value', []);
$ip = trim((string) ($value['ip_address'] ?? ''));
return [
'tools_running' => $ip !== '' && $ip !== '0.0.0.0',
'ip_address' => $ip ?: null,
'os_family' => isset($value['family']) ? strtoupper((string) $value['family']) : null,
];
} catch (\Throwable $e) {
Log::warning('VmProvisionService::getVmGuestInfo error', [
'vm_id' => $vmId, 'error' => $e->getMessage(),
]);
return ['tools_running' => false, 'ip_address' => null, 'os_family' => null];
}
}
// -------------------------------------------------------------------------
// Password
// -------------------------------------------------------------------------
/**
* Generate a cryptographically secure 16-char password.
* Charset deliberately excludes % (breaks cmd.exe env expansion)
* and " (breaks PowerShell argument quoting).
*/
public function generatePassword(int $length = 16): string
{
$sets = [
'ABCDEFGHJKLMNPQRSTUVWXYZ',
'abcdefghjkmnpqrstuvwxyz',
'23456789',
'!@#$*', // no % or " — safe in cmd.exe double-quoted arguments
];
$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);
}
public function encryptPassword(string $plaintext): string
{
return SignRSA::EncryptFunc($plaintext);
}
// -------------------------------------------------------------------------
// Persistence
// -------------------------------------------------------------------------
/**
* Update password only — IP already set during VM creation.
*/
public function updatePassword(int $moduleVmwareId, string $plainPassword): void
{
ModuleVmware::where('id', $moduleVmwareId)->update([
'password' => $this->encryptPassword($plainPassword),
'plain_password' => $plainPassword,
]);
Log::info('VmProvisionService::updatePassword saved', [
'module_vmware_id' => $moduleVmwareId,
]);
}
/**
* Update password + overwrite IP (used when real guest IP differs from pool IP).
*/
public function savePassword(int $moduleVmwareId, string $ip, string $plainPassword): void
{
ModuleVmware::where('id', $moduleVmwareId)->update([
'ip' => $ip,
'password' => $this->encryptPassword($plainPassword),
'plain_password' => $plainPassword,
]);
Log::info('VmProvisionService::savePassword saved', [
'module_vmware_id' => $moduleVmwareId,
'ip' => $ip,
]);
}
}