fix(auth): restrict Sentinel access and register S3 policy
This commit is contained in:
parent
46a9578d69
commit
d2deaa8363
9 changed files with 146 additions and 10 deletions
|
|
@ -3,11 +3,14 @@
|
|||
namespace App\Livewire\Server\Sentinel;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class Logs extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public ?Server $server = null;
|
||||
|
||||
public array $parameters = [];
|
||||
|
|
@ -19,7 +22,11 @@ public function mount(): void
|
|||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->authorize('viewSentinel', $this->server);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
namespace App\Livewire\Server\Sentinel;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public ?Server $server = null;
|
||||
|
||||
public array $parameters = [];
|
||||
|
|
@ -19,7 +22,11 @@ public function mount(): void
|
|||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->authorize('viewSentinel', $this->server);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
|
|
|
|||
|
|
@ -79,6 +79,14 @@ public function manageSentinel(User $user, Server $server): bool
|
|||
return $user->isAdminOfTeam($server->team_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view Sentinel configuration and logs.
|
||||
*/
|
||||
public function viewSentinel(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdminOfTeam($server->team_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can manage CA certificates.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\PushoverNotificationSettings;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
|
|
@ -51,6 +52,7 @@
|
|||
use App\Policies\PrivateKeyPolicy;
|
||||
use App\Policies\ProjectPolicy;
|
||||
use App\Policies\ResourceCreatePolicy;
|
||||
use App\Policies\S3StoragePolicy;
|
||||
use App\Policies\ServerPolicy;
|
||||
use App\Policies\ServiceApplicationPolicy;
|
||||
use App\Policies\ServiceDatabasePolicy;
|
||||
|
|
@ -109,6 +111,9 @@ class AuthServiceProvider extends ServiceProvider
|
|||
// Instance settings policy
|
||||
InstanceSettings::class => InstanceSettingsPolicy::class,
|
||||
|
||||
// S3 storage policy
|
||||
S3Storage::class => S3StoragePolicy::class,
|
||||
|
||||
// Team policy
|
||||
Team::class => TeamPolicy::class,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<div class="sub-menu-wrapper">
|
||||
<a class="{{ request()->routeIs('server.sentinel') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
|
||||
href="{{ route('server.sentinel', $parameters) }}">
|
||||
<span class="menu-item-label">Configuration</span>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('server.sentinel.logs') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
|
||||
href="{{ route('server.sentinel.logs', $parameters) }}">
|
||||
<span class="menu-item-label">Logs</span>
|
||||
</a>
|
||||
@can('viewSentinel', $server)
|
||||
<a class="{{ request()->routeIs('server.sentinel') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
|
||||
href="{{ route('server.sentinel', $parameters) }}">
|
||||
<span class="menu-item-label">Configuration</span>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('server.sentinel.logs') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
|
||||
href="{{ route('server.sentinel.logs', $parameters) }}">
|
||||
<span class="menu-item-label">Logs</span>
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar
|
|||
@endif
|
||||
</a>
|
||||
@endif
|
||||
@if ($server->isFunctional() && !$server->isSwarm() && !$server->settings->is_build_server)
|
||||
@if ($server->isFunctional() && !$server->isSwarm() && !$server->settings->is_build_server && auth()->user()?->can('viewSentinel', $server))
|
||||
<a class="{{ request()->routeIs('server.sentinel') || request()->routeIs('server.sentinel.*') ? 'dark:text-white' : '' }} flex items-center gap-1" href="{{ route('server.sentinel', [
|
||||
'server_uuid' => data_get($server, 'uuid'),
|
||||
]) }}" {{ wireNavigate() }}>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Middleware\PreventRequestsDuringMaintenance;
|
||||
use App\Livewire\Server\Navbar as ServerNavbar;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
|
|
@ -13,7 +14,9 @@
|
|||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
$this->withoutMiddleware(PreventRequestsDuringMaintenance::class);
|
||||
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::updateOrCreate(['id' => 0], ['id' => 0]));
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
|
|
@ -217,6 +220,44 @@
|
|||
$this->get("/server/{$this->server->uuid}")->assertSuccessful();
|
||||
});
|
||||
|
||||
test('admin can access sentinel configuration and logs pages', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get(route('server.sentinel', ['server_uuid' => $this->server->uuid]))
|
||||
->assertSuccessful();
|
||||
|
||||
$this->get(route('server.sentinel.logs', ['server_uuid' => $this->server->uuid]))
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('member cannot access sentinel configuration and logs pages', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get(route('server.sentinel', ['server_uuid' => $this->server->uuid]))
|
||||
->assertForbidden();
|
||||
|
||||
$this->get(route('server.sentinel.logs', ['server_uuid' => $this->server->uuid]))
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('sentinel navigation is only visible to team admins', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get("/server/{$this->server->uuid}")
|
||||
->assertSuccessful()
|
||||
->assertDontSee(route('server.sentinel', ['server_uuid' => $this->server->uuid]));
|
||||
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get("/server/{$this->server->uuid}")
|
||||
->assertSuccessful()
|
||||
->assertSee(route('server.sentinel', ['server_uuid' => $this->server->uuid]));
|
||||
});
|
||||
|
||||
test('unauthenticated user cannot access server page', function () {
|
||||
$this->get("/server/{$this->server->uuid}")->assertRedirect('/login');
|
||||
});
|
||||
|
|
|
|||
54
tests/Feature/S3StoragePolicyRegistrationTest.php
Normal file
54
tests/Feature/S3StoragePolicyRegistrationTest.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use App\Policies\S3StoragePolicy;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
test('s3 storage model resolves its registered policy through the gate', function () {
|
||||
expect(Gate::getPolicyFor(S3Storage::class))->toBeInstanceOf(S3StoragePolicy::class);
|
||||
});
|
||||
|
||||
test('s3 storage create ability is enforced through the registered policy', function () {
|
||||
$team = Team::factory()->create();
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->teams()->attach($team, ['role' => 'admin']);
|
||||
|
||||
$member = User::factory()->create();
|
||||
$member->teams()->attach($team, ['role' => 'member']);
|
||||
|
||||
$this->actingAs($admin);
|
||||
session(['currentTeam' => $team]);
|
||||
|
||||
expect($admin->can('create', S3Storage::class))->toBeTrue()
|
||||
->and($member->can('create', S3Storage::class))->toBeFalse();
|
||||
});
|
||||
|
||||
test('s3 storage validate connection ability is enforced through the registered policy', function () {
|
||||
$team = Team::factory()->create();
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->teams()->attach($team, ['role' => 'admin']);
|
||||
|
||||
$member = User::factory()->create();
|
||||
$member->teams()->attach($team, ['role' => 'member']);
|
||||
|
||||
$storage = S3Storage::create([
|
||||
'team_id' => $team->id,
|
||||
'name' => 'Backups',
|
||||
'description' => 'Team backup storage',
|
||||
'region' => 'us-east-1',
|
||||
'key' => 'access-key',
|
||||
'secret' => 'secret-key',
|
||||
'bucket' => 'coolify-backups',
|
||||
'endpoint' => 'https://s3.us-east-1.amazonaws.com',
|
||||
]);
|
||||
|
||||
expect($admin->can('validateConnection', $storage))->toBeTrue()
|
||||
->and($member->can('validateConnection', $storage))->toBeFalse();
|
||||
});
|
||||
|
|
@ -152,6 +152,18 @@
|
|||
|
||||
$policy = new ServerPolicy;
|
||||
expect($policy->manageSentinel($user, $server))->toBeTrue();
|
||||
expect($policy->viewSentinel($user, $server))->toBeTrue();
|
||||
expect($policy->manageCaCertificate($user, $server))->toBeTrue();
|
||||
expect($policy->viewSecurity($user, $server))->toBeTrue();
|
||||
});
|
||||
|
||||
it('denies team member to view sentinel', function () {
|
||||
$user = Mockery::mock(User::class)->makePartial();
|
||||
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
|
||||
|
||||
$server = Mockery::mock(Server::class)->makePartial();
|
||||
$server->team_id = 1;
|
||||
|
||||
$policy = new ServerPolicy;
|
||||
expect($policy->viewSentinel($user, $server))->toBeFalse();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue