Files

232 lines
7.6 KiB
PHP

<?php
namespace App\Http\Services\vCenter\Concerns;
use App\Http\Facade\SignRSA;
use App\Models\InfraManagement;
use Exception;
use Illuminate\Support\Facades\Log;
use phpseclib3\Net\SSH2;
/**
* SSH / PowerShell execution via jumphost.
*
* Requires on the host class:
* public ?string $token
* public string $apiUri
* private string $username
* private string $password
*/
trait ManagesSsh
{
// ─── Auth ────────────────────────────────────────────────────────────────
private function LoginAuth(): SSH2|false
{
try {
$infra = InfraManagement::where('type', 'jumphost')
->where('status', true)
->first();
if (!$infra) {
Log::error('SSH Login Failed: No active jumphost configuration found in InfraManagement.');
return false;
}
$sshHost = $infra->host;
$sshPort = 22;
if (isset($infra->meta_data['port'])) {
$sshPort = (int) $infra->meta_data['port'];
}
$connection = new SSH2($sshHost, $sshPort);
if ($connection->login($infra->username, SignRSA::DecryptFunc($infra->password))) {
$connection->setTimeout(0);
return $connection;
}
Log::error('SSH Login Failed: Invalid credentials for jumphost.');
return false;
} catch (Exception $error) {
Log::error('SSH Login Failed: ' . $error->getMessage());
return false;
}
}
public function loginSSH(): bool
{
$conn = $this->LoginAuth();
if ($conn) {
Log::info('✅ Connected to vCenter via SSH successfully');
return true;
}
Log::error('❌ Failed to connect to vCenter via SSH');
return false;
}
// ─── Command variants ────────────────────────────────────────────────────
/**
* Run a PowerShell command using a stored credential file on the jumphost.
* Uses C:\secure\vcenter.cred for vCenter auth — no plain-text password in command.
*/
protected function Command($cmd): string
{
$auth = $this->LoginAuth();
if (!$auth) {
return 'Login_Fail';
}
try {
$psCommand = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "'
. 'Import-Module VMware.PowerCLI; '
. 'Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false -Scope Session; '
. '$cred = Import-Clixml -Path \'C:\secure\vcenter.cred\'; '
. 'Connect-VIServer -Server \'' . $this->apiUri . '\' -Credential $cred; '
. $cmd . '; '
. 'Disconnect-VIServer -Confirm:$false" 2>&1';
Log::channel('Custom')->info('PowerShell Command: ' . substr($psCommand, 0, 200) . '...');
$out = $auth->exec($psCommand);
$errOut = $auth->getStdError();
Log::channel('Custom')->info('PowerShell Output length: ' . strlen($out));
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Exception $e) {
Log::channel('Custom')->error('Command Exception: ' . $e->getMessage());
return $e->getMessage();
}
}
/**
* Run a raw PowerShell command directly — no vCenter connect wrapper.
*/
public function runPsCommand(string $psCommand): string
{
$auth = $this->LoginAuth();
if (!$auth) {
return 'Login_Fail';
}
$action = 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass '
. '-Command "' . str_replace('"', '\"', $psCommand) . '"';
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return 'Error: ' . $e->getMessage();
}
}
/**
* Run a command using EncodedCommand — connects to vCenter with plain credentials.
* Safer for complex scripts that would break with inline quoting.
*/
protected function CommandRSP(string $cmd): string
{
$auth = $this->LoginAuth();
if (!$auth) {
return 'Login_Fail';
}
$esc = fn(string $v) => str_replace("'", "''", $v);
$script = strtr(
<<<'PS'
$ProgressPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
Import-Module VMware.PowerCLI -ErrorAction Stop
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
& {
<CMD>
} | Out-String
PS,
[
'<SERVER>' => $esc($this->apiUri ?? ''),
'<USER>' => $esc($this->username ?? ''),
'<PASS>' => $esc($this->password ?? ''),
'<CMD>' => $cmd,
]
);
$encoded = base64_encode(mb_convert_encoding($script, 'UTF-16LE', 'UTF-8'));
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return 'Error: ' . $e->getMessage();
}
}
/**
* Run a command using EncodedCommand — variant used for RDP port changes.
* Optionally strips raw output down to the first JSON block found.
*/
protected function CommandChangePort(string $cmd, bool $expectJson = false): string
{
$auth = $this->LoginAuth();
if (!$auth) {
return 'Login_Fail';
}
$esc = fn(string $v) => str_replace("'", "''", $v);
$script = strtr(
<<<'PS'
$ProgressPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Import-Module VMware.PowerCLI -ErrorAction Stop
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
& {
<CMD>
}
PS,
[
'<SERVER>' => $esc($this->apiUri ?? ''),
'<USER>' => $esc($this->username ?? ''),
'<PASS>' => $esc($this->password ?? ''),
'<CMD>' => $cmd,
]
);
$encoded = base64_encode(mb_convert_encoding($script, 'UTF-16LE', 'UTF-8'));
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
if ($expectJson) {
$trimmed = trim($out);
if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $trimmed, $m)) {
return $m[1];
}
return $trimmed;
}
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return 'Error: ' . $e->getMessage();
}
}
}