Files
domovoy/app/Services/Ssh/SshCredentialTester.php
mirv dc3a466944 Выполнение шага 5:
• Updated Plan
  └ Продолжаю итерацию 5 как отдельный рабочий срез поверх текущих SSH credentials.
    □ Добавить модель/миграцию/repository для host_scans и покрыть repository тестами
    □ Добавить SSH command runner и LinuxHostScanner с whitelist read-only команд
    □ Подключить controller/routes/UI для запуска deep scan и просмотра результата
    □ Прогнать проверки и зафиксировать ограничения Docker-верификации
2026-05-29 17:34:27 +08:00

72 lines
2.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Domovoy\Services\Ssh;
use Domovoy\Models\Credential;
use Domovoy\Services\Security\CredentialVault;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Net\SSH2;
class SshCredentialTester
{
/** @var callable(string, int, int): object */
private $clientFactory;
/**
* @param callable(string, int, int): object|null $clientFactory
* @param array<string, int> $settings
*/
public function __construct(
private CredentialVault $vault,
?callable $clientFactory = null,
private array $settings = []
) {
$this->clientFactory = $clientFactory ?? static fn (string $host, int $port, int $timeout): SSH2 => new SSH2($host, $port, $timeout);
}
/** @return array{status: string, message: string} */
public function test(Credential $credential, string $host): array
{
if ($host === '') {
return ['status' => 'failed', 'message' => 'У устройства не указан IP или hostname'];
}
try {
$client = ($this->clientFactory)(
$host,
$credential->port,
(int)($this->settings['connect_timeout'] ?? 5)
);
$auth = $this->buildAuth($credential);
$ok = $client->login($credential->username, $auth);
if ($ok) {
return ['status' => 'ok', 'message' => 'Подключение успешно'];
}
return ['status' => 'failed', 'message' => 'SSH-аутентификация не прошла'];
} catch (\Throwable $e) {
return ['status' => 'failed', 'message' => 'Ошибка SSH: ' . $e->getMessage()];
}
}
private function buildAuth(Credential $credential): mixed
{
if ($credential->authMethod === 'private_key') {
if ($credential->encryptedPrivateKey === null) {
throw new \RuntimeException('Private key is empty');
}
return PublicKeyLoader::loadPrivateKey($this->vault->decrypt($credential->encryptedPrivateKey));
}
if ($credential->encryptedSecret === null) {
throw new \RuntimeException('Password is empty');
}
return $this->vault->decrypt($credential->encryptedSecret);
}
}