feat(terminal-access): implement terminal access control for servers and containers, including UI updates and backend logic
This commit is contained in:
@@ -49,17 +49,18 @@ class ExecuteContainerCommand extends Component
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->containers = collect();
|
||||
$this->servers = collect();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$this->type = 'application';
|
||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
if ($this->resource->destination->server->isFunctional() && $this->resource->destination->server->isTerminalEnabled()) {
|
||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
||||
}
|
||||
foreach ($this->resource->additional_servers as $server) {
|
||||
if ($server->isFunctional()) {
|
||||
if ($server->isFunctional() && $server->isTerminalEnabled()) {
|
||||
$this->servers = $this->servers->push($server);
|
||||
}
|
||||
}
|
||||
@@ -71,14 +72,14 @@ class ExecuteContainerCommand extends Component
|
||||
abort(404);
|
||||
}
|
||||
$this->resource = $resource;
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
if ($this->resource->destination->server->isFunctional() && $this->resource->destination->server->isTerminalEnabled()) {
|
||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
||||
}
|
||||
$this->loadContainers();
|
||||
} elseif (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
if ($this->resource->server->isFunctional()) {
|
||||
if ($this->resource->server->isFunctional() && $this->resource->server->isTerminalEnabled()) {
|
||||
$this->servers = $this->servers->push($this->resource->server);
|
||||
}
|
||||
$this->loadContainers();
|
||||
|
||||
@@ -4,9 +4,12 @@ namespace App\Livewire\Server;
|
||||
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Jobs\RegenerateSslCertJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -36,6 +39,9 @@ class Advanced extends Component
|
||||
#[Validate(['integer', 'min:1'])]
|
||||
public int $dynamicTimeout = 1;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isTerminalEnabled = false;
|
||||
|
||||
public function mount(string $server_uuid)
|
||||
{
|
||||
try {
|
||||
@@ -63,6 +69,37 @@ class Advanced extends Component
|
||||
$this->showCertificate = ! $this->showCertificate;
|
||||
}
|
||||
|
||||
public function toggleTerminal($password)
|
||||
{
|
||||
try {
|
||||
// Check if user is admin or owner
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
throw new \Exception('Only team administrators and owners can modify terminal access.');
|
||||
}
|
||||
|
||||
// Verify password unless two-step confirmation is disabled
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle the terminal setting
|
||||
$this->server->settings->is_terminal_enabled = ! $this->server->settings->is_terminal_enabled;
|
||||
$this->server->settings->save();
|
||||
|
||||
// Update the local property
|
||||
$this->isTerminalEnabled = $this->server->settings->is_terminal_enabled;
|
||||
|
||||
$status = $this->isTerminalEnabled ? 'enabled' : 'disabled';
|
||||
$this->dispatch('success', "Terminal access has been {$status}.");
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveCaCertificate()
|
||||
{
|
||||
try {
|
||||
@@ -149,6 +186,7 @@ class Advanced extends Component
|
||||
$this->dynamicTimeout = $this->server->settings->dynamic_timeout;
|
||||
$this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold;
|
||||
$this->serverDiskUsageCheckFrequency = $this->server->settings->server_disk_usage_check_frequency;
|
||||
$this->isTerminalEnabled = $this->server->settings->is_terminal_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ class Index extends Component
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
abort(403);
|
||||
}
|
||||
$this->servers = Server::isReachable()->get();
|
||||
$this->servers = Server::isReachable()->get()->filter(function ($server) {
|
||||
return $server->isTerminalEnabled();
|
||||
});
|
||||
}
|
||||
|
||||
public function loadContainers()
|
||||
|
||||
@@ -952,6 +952,11 @@ $schema://$host {
|
||||
}
|
||||
}
|
||||
|
||||
public function isTerminalEnabled()
|
||||
{
|
||||
return $this->settings->is_terminal_enabled ?? false;
|
||||
}
|
||||
|
||||
public function isSwarm()
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_manager') || data_get($this, 'settings.is_swarm_worker');
|
||||
|
||||
@@ -28,6 +28,7 @@ use OpenApi\Attributes as OA;
|
||||
'is_sentinel_enabled' => ['type' => 'boolean'],
|
||||
'is_swarm_manager' => ['type' => 'boolean'],
|
||||
'is_swarm_worker' => ['type' => 'boolean'],
|
||||
'is_terminal_enabled' => ['type' => 'boolean'],
|
||||
'is_usable' => ['type' => 'boolean'],
|
||||
'logdrain_axiom_api_key' => ['type' => 'string'],
|
||||
'logdrain_axiom_dataset_name' => ['type' => 'string'],
|
||||
@@ -59,6 +60,7 @@ class ServerSetting extends Model
|
||||
'sentinel_token' => 'encrypted',
|
||||
'is_reachable' => 'boolean',
|
||||
'is_usable' => 'boolean',
|
||||
'is_terminal_enabled' => 'boolean',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->boolean('is_terminal_enabled')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('is_terminal_enabled');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -23,11 +23,15 @@
|
||||
'dispatchEventType' => 'success',
|
||||
'dispatchEventMessage' => '',
|
||||
'ignoreWire' => true,
|
||||
'temporaryDisableTwoStepConfirmation' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
use App\Models\InstanceSettings;
|
||||
$disableTwoStepConfirmation = data_get(InstanceSettings::get(), 'disable_two_step_confirmation');
|
||||
if ($temporaryDisableTwoStepConfirmation) {
|
||||
$disableTwoStepConfirmation = false;
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div {{ $ignoreWire ? 'wire:ignore' : '' }} x-data="{
|
||||
@@ -262,7 +266,7 @@
|
||||
{{ $shortConfirmationLabel }}
|
||||
</label>
|
||||
<input type="text" x-model="userConfirmationText"
|
||||
class="p-2 mt-1 w-full text-black rounded-sm input">
|
||||
class="p-2 mt-1 px-3 w-full rounded-sm input">
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</div>
|
||||
@else
|
||||
@if (count($containers) === 0)
|
||||
<div class="pt-4">No containers are running.</div>
|
||||
<div class="pt-4">No containers are running on this server or terminal access is disabled.</div>
|
||||
@else
|
||||
@if (count($containers) === 1)
|
||||
<form class="w-full pt-4" wire:submit="$dispatchSelf('connectToContainer')"
|
||||
|
||||
@@ -14,6 +14,46 @@
|
||||
<div class="mb-4">Advanced configuration for your server.</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<h3>Terminal Access</h3>
|
||||
<x-helper
|
||||
helper="Control whether terminal access is available for this server and its containers.<br/>Only team
|
||||
administrators and owners can modify this setting." />
|
||||
@if ($isTerminalEnabled)
|
||||
<span
|
||||
class="px-2 py-1 text-xs font-semibold text-green-800 bg-green-100 rounded dark:text-green-100 dark:bg-green-800">
|
||||
Enabled
|
||||
</span>
|
||||
@else
|
||||
<span
|
||||
class="px-2 py-1 text-xs font-semibold text-red-800 bg-red-100 rounded dark:text-red-100 dark:bg-red-800">
|
||||
Disabled
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-4 pt-4">
|
||||
@if (auth()->user()->isAdmin())
|
||||
<div wire:key="terminal-access-change-{{ $isTerminalEnabled }}" class="pb-4">
|
||||
<x-modal-confirmation title="Confirm Terminal Access Change?"
|
||||
buttonTitle="{{ $isTerminalEnabled ? 'Disable Terminal' : 'Enable Terminal' }}"
|
||||
submitAction="toggleTerminal" :actions="[
|
||||
$isTerminalEnabled
|
||||
? 'This will disable terminal access for this server and all its containers.'
|
||||
: 'This will enable terminal access for this server and all its containers.',
|
||||
$isTerminalEnabled
|
||||
? 'Users will no longer be able to access terminal views from the UI.'
|
||||
: 'Users will be able to access terminal views from the UI.',
|
||||
'This change will take effect immediately.',
|
||||
]" confirmationText="{{ $server->name }}"
|
||||
shortConfirmationLabel="Server Name"
|
||||
step3ButtonText="{{ $isTerminalEnabled ? 'Disable Terminal' : 'Enable Terminal' }}">
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Disk Usage</h3>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col">
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<x-loading text="Loading servers and containers..." />
|
||||
</div>
|
||||
@else
|
||||
@if ($servers->count() > 0)
|
||||
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
|
||||
wire:submit="$dispatchSelf('connectToContainer')">
|
||||
<x-forms.select id="server" required wire:model.live="selected_uuid">
|
||||
@@ -33,6 +34,9 @@
|
||||
</x-forms.select>
|
||||
<x-forms.button type="submit">Connect</x-forms.button>
|
||||
</form>
|
||||
@else
|
||||
<div>No servers with terminal access found.</div>
|
||||
@endif
|
||||
@endif
|
||||
<livewire:project.shared.terminal />
|
||||
</div>
|
||||
|
||||
@@ -153,7 +153,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::post('/terminal/auth/ips', function () {
|
||||
if (auth()->check()) {
|
||||
$team = auth()->user()->currentTeam();
|
||||
$ipAddresses = $team->servers()->pluck('ip')->toArray();
|
||||
$ipAddresses = $team->servers->where('settings.is_terminal_enabled', true)->pluck('ip')->toArray();
|
||||
|
||||
return response()->json(['ipAddresses' => $ipAddresses], 200);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user