debianProfile(); $detector = Mockery::mock(OsDetector::class); $detector->shouldReceive('detect')->andReturn($os); $captured = ''; $fleet = Mockery::mock(FleetService::class); $fleet->shouldReceive('runPrivileged') ->andReturnUsing(function ($server, $cmd) use (&$captured) { $captured = $cmd; // fail2ban running from a source/`rc` install: present + active. return ['ok' => true, 'output' => "fail2ban 1 active\nfirewall 1 active\nautoupdate 0 inactive"]; }); $service = new HardeningService($fleet, Mockery::mock(FirewallService::class), $detector); $rows = $service->state(new Server); // fail2ban installed-probe gains the binary fallback (was dpkg ^ii only — the bug). $this->assertStringContainsString('command -v fail2ban-client', $captured); // firewall installed-probe gains the package-manager fallback (was command -v only). $this->assertStringContainsString('dpkg -l ufw', $captured); $f2b = collect($rows)->firstWhere('key', 'fail2ban'); $this->assertTrue($f2b['featureOn'], 'fail2ban active must read as on'); } public function test_autoupdate_row_is_neutral_and_never_insecure(): void { $os = $this->debianProfile(); $detector = Mockery::mock(OsDetector::class); $detector->shouldReceive('detect')->andReturn($os); $fleet = Mockery::mock(FleetService::class); $fleet->shouldReceive('runPrivileged')->andReturn([ 'ok' => true, // auto-update reported INACTIVE by the host: 'output' => "fail2ban 1 active\nfirewall 1 active\nautoupdate 1 inactive", ]); $service = new HardeningService($fleet, Mockery::mock(FirewallService::class), $detector); $rows = $service->state(new Server); $au = collect($rows)->firstWhere('key', 'unattended'); $this->assertTrue($au['secure'], 'auto-updates must never read as insecure'); $this->assertTrue($au['neutral'] ?? false, 'auto-updates row must be flagged neutral'); $this->assertFalse($au['featureOn'], 'inactive auto-updates → toggle shows enable'); } /** * Lock-out guard: when Clusev connects AS root, disabling SSH root login (PermitRootLogin no) * would sever its own access — it must be refused before any command runs. */ public function test_disabling_root_login_is_refused_when_clusev_connects_as_root(): void { $detector = Mockery::mock(OsDetector::class); $detector->shouldReceive('detect')->andReturn($this->debianProfile()); $fleet = Mockery::mock(FleetService::class); $fleet->shouldNotReceive('runPrivileged'); // guard must short-circuit before any command $server = new Server; $server->setRelation('credential', new SshCredential(['username' => 'root'])); $service = new HardeningService($fleet, Mockery::mock(FirewallService::class), $detector); $res = $service->apply($server, 'ssh_root', false); $this->assertFalse($res['ok']); $this->assertSame(__('backend.ssh_root_self_lockout'), $res['output']); } public function test_disabling_root_login_proceeds_for_a_non_root_credential(): void { $detector = Mockery::mock(OsDetector::class); $detector->shouldReceive('detect')->andReturn($this->debianProfile()); $fleet = Mockery::mock(FleetService::class); $fleet->shouldReceive('runPrivileged')->once()->andReturn(['ok' => true, 'output' => '']); $server = new Server; $server->setRelation('credential', new SshCredential(['username' => 'deploy'])); $service = new HardeningService($fleet, Mockery::mock(FirewallService::class), $detector); $res = $service->apply($server, 'ssh_root', false); $this->assertTrue($res['ok']); } }