Выполнение шага 5:
• Updated Plan
└ Продолжаю итерацию 5 как отдельный рабочий срез поверх текущих SSH credentials.
□ Добавить модель/миграцию/repository для host_scans и покрыть repository тестами
□ Добавить SSH command runner и LinuxHostScanner с whitelist read-only команд
□ Подключить controller/routes/UI для запуска deep scan и просмотра результата
□ Прогнать проверки и зафиксировать ограничения Docker-верификации
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Tests\Services;
|
||||
|
||||
use Domovoy\Models\Credential;
|
||||
use Domovoy\Models\Device;
|
||||
use Domovoy\Services\HostScan\CommandWhitelist;
|
||||
use Domovoy\Services\HostScan\LinuxHostScanner;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class LinuxHostScannerTest extends TestCase
|
||||
{
|
||||
public function testCollectsReadOnlyCommandsAndWritesRawJson(): void
|
||||
{
|
||||
$storagePath = sys_get_temp_dir() . '/domovoy-host-scan-test-' . uniqid();
|
||||
mkdir($storagePath, 0777, true);
|
||||
$runner = new FakeCommandRunner([
|
||||
'hostnamectl --static' => ['exit_code' => 0, 'stdout' => "server-1\n", 'stderr' => ''],
|
||||
'cat /etc/os-release' => ['exit_code' => 0, 'stdout' => "PRETTY_NAME=\"Debian GNU/Linux 12\"\n", 'stderr' => ''],
|
||||
'uname -r' => ['exit_code' => 0, 'stdout' => "6.1.0\n", 'stderr' => ''],
|
||||
'df -h --output=source,size,used,avail,pcent,target' => ['exit_code' => 0, 'stdout' => "Filesystem Size Used Avail Use% Mounted on\n/dev/sda1 20G 10G 10G 50% /\n", 'stderr' => ''],
|
||||
'systemctl --failed --no-pager --plain' => ['exit_code' => 0, 'stdout' => "0 loaded units listed.\n", 'stderr' => ''],
|
||||
'crontab -l' => ['exit_code' => 1, 'stdout' => '', 'stderr' => 'no crontab for root'],
|
||||
]);
|
||||
$scanner = new LinuxHostScanner($runner, new CommandWhitelist(), $storagePath);
|
||||
|
||||
$device = new Device();
|
||||
$device->id = 5;
|
||||
$device->primaryIp = '192.168.1.10';
|
||||
$credential = new Credential();
|
||||
$credential->id = 9;
|
||||
|
||||
$result = $scanner->scan($device, $credential);
|
||||
|
||||
self::assertSame('server-1', $result['summary']['hostname']);
|
||||
self::assertSame('Debian GNU/Linux 12', $result['summary']['os']);
|
||||
self::assertSame('6.1.0', $result['summary']['kernel']);
|
||||
self::assertFileExists($result['raw_path']);
|
||||
self::assertStringContainsString('hostnamectl', file_get_contents($result['raw_path']));
|
||||
}
|
||||
}
|
||||
|
||||
final class FakeCommandRunner
|
||||
{
|
||||
/** @param array<string, array{exit_code: int, stdout: string, stderr: string}> $responses */
|
||||
public function __construct(private array $responses)
|
||||
{
|
||||
}
|
||||
|
||||
/** @return array{exit_code: int, stdout: string, stderr: string} */
|
||||
public function run(Credential $credential, string $host, array $command): array
|
||||
{
|
||||
$key = implode(' ', $command);
|
||||
return $this->responses[$key] ?? ['exit_code' => 127, 'stdout' => '', 'stderr' => 'missing fake response'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Tests\Services;
|
||||
|
||||
use Domovoy\Models\Credential;
|
||||
use Domovoy\Services\Security\CredentialVault;
|
||||
use Domovoy\Services\Ssh\SshCredentialTester;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class SshCredentialTesterTest extends TestCase
|
||||
{
|
||||
public function testTestsPasswordCredentialWithDecryptedSecret(): void
|
||||
{
|
||||
$vault = new CredentialVault('test-key-material');
|
||||
$credential = new Credential();
|
||||
$credential->username = 'root';
|
||||
$credential->authMethod = 'password';
|
||||
$credential->encryptedSecret = $vault->encrypt('secret-password');
|
||||
|
||||
$tester = new SshCredentialTester($vault, function (string $host, int $port, int $timeout) {
|
||||
self::assertSame('192.168.1.10', $host);
|
||||
self::assertSame(22, $port);
|
||||
self::assertSame(5, $timeout);
|
||||
return new FakeSshClient();
|
||||
}, ['connect_timeout' => 5]);
|
||||
|
||||
$result = $tester->test($credential, '192.168.1.10');
|
||||
|
||||
self::assertSame('ok', $result['status']);
|
||||
self::assertSame('Подключение успешно', $result['message']);
|
||||
self::assertSame('root', FakeSshClient::$lastUsername);
|
||||
self::assertSame('secret-password', FakeSshClient::$lastPassword);
|
||||
}
|
||||
}
|
||||
|
||||
final class FakeSshClient
|
||||
{
|
||||
public static ?string $lastUsername = null;
|
||||
public static ?string $lastPassword = null;
|
||||
|
||||
public function login(string $username, string $password): bool
|
||||
{
|
||||
self::$lastUsername = $username;
|
||||
self::$lastPassword = $password;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user