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, ]); } }