clusev/tests/Feature/SshKeyProvisionerTest.php

106 lines
4.2 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Server;
use App\Services\FleetService;
use App\Services\HardeningService;
use App\Services\SshKeyProvisioner;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
use Tests\TestCase;
class SshKeyProvisionerTest extends TestCase
{
use RefreshDatabase;
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
private function passwordServer(): Server
{
$server = Server::create(['name' => 'box', 'ip' => '10.0.0.5', 'ssh_port' => 22, 'status' => 'online']);
$server->credential()->create(['username' => 'admin', 'auth_type' => 'password', 'secret' => 'pw-secret']);
return $server->fresh('credential');
}
public function test_happy_path_installs_key_switches_credential_and_disables_password(): void
{
$server = $this->passwordServer();
$fleet = Mockery::mock(FleetService::class);
$fleet->shouldReceive('addAuthorizedKey')->once();
$fleet->shouldReceive('testConnection')->once()->andReturn(['ok' => true, 'error' => null]);
$hardening = Mockery::mock(HardeningService::class);
$hardening->shouldReceive('apply')->once()->with(Mockery::type(Server::class), 'ssh_password', false)
->andReturn(['ok' => true, 'output' => '']);
$result = (new SshKeyProvisioner($fleet, $hardening))->enableKeyOnlyAccess($server);
$this->assertTrue($result['ok']);
$this->assertNotEmpty($result['privateKey']);
$this->assertStringContainsString('ssh-ed25519', $result['publicKey']);
$cred = $server->fresh('credential')->credential;
$this->assertSame('key', $cred->auth_type);
$this->assertStringContainsString('PRIVATE KEY', $cred->secret); // now holds the private PEM
}
public function test_verify_failure_rolls_back_the_credential_and_does_not_disable_password(): void
{
$server = $this->passwordServer();
$fleet = Mockery::mock(FleetService::class);
$fleet->shouldReceive('addAuthorizedKey')->once();
$fleet->shouldReceive('testConnection')->once()->andReturn(['ok' => false, 'error' => 'auth failed']);
$hardening = Mockery::mock(HardeningService::class);
$hardening->shouldNotReceive('apply'); // never disable password if the key didn't verify
$result = (new SshKeyProvisioner($fleet, $hardening))->enableKeyOnlyAccess($server);
$this->assertFalse($result['ok']);
$this->assertNotEmpty($result['error']);
$cred = $server->fresh('credential')->credential;
$this->assertSame('password', $cred->auth_type, 'credential restored to password');
$this->assertSame('pw-secret', $cred->secret, 'original secret restored');
}
public function test_already_key_credential_skips_keygen_and_just_disables_password(): void
{
$server = Server::create(['name' => 'box2', 'ip' => '10.0.0.6', 'ssh_port' => 22, 'status' => 'online']);
$server->credential()->create(['username' => 'admin', 'auth_type' => 'key', 'secret' => 'EXISTING-PRIVATE-KEY']);
$server = $server->fresh('credential');
$fleet = Mockery::mock(FleetService::class);
$fleet->shouldNotReceive('addAuthorizedKey'); // already key — no new key generated
$fleet->shouldNotReceive('testConnection');
$hardening = Mockery::mock(HardeningService::class);
$hardening->shouldReceive('apply')->once()->with(Mockery::type(Server::class), 'ssh_password', false)
->andReturn(['ok' => true, 'output' => '']);
$result = (new SshKeyProvisioner($fleet, $hardening))->enableKeyOnlyAccess($server);
$this->assertTrue($result['ok']);
$this->assertArrayNotHasKey('privateKey', $result); // nothing new to reveal
$this->assertSame('EXISTING-PRIVATE-KEY', $server->fresh('credential')->credential->secret);
}
public function test_no_credential_returns_error(): void
{
$server = Server::create(['name' => 'box3', 'ip' => '10.0.0.7', 'ssh_port' => 22, 'status' => 'online']);
$result = (new SshKeyProvisioner(Mockery::mock(FleetService::class), Mockery::mock(HardeningService::class)))
->enableKeyOnlyAccess($server);
$this->assertFalse($result['ok']);
}
}