mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-19 18:01:37 +00:00
Refactor firewall and add edit rule (#488)
This commit is contained in:
parent
e2b9d18a71
commit
8c7c3d2192
@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\FirewallRule;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\Firewall\Firewall;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CreateRule
|
||||
{
|
||||
public function create(Server $server, array $input): FirewallRule
|
||||
{
|
||||
$rule = new FirewallRule([
|
||||
'server_id' => $server->id,
|
||||
'type' => $input['type'],
|
||||
'protocol' => $input['protocol'],
|
||||
'port' => $input['port'],
|
||||
'source' => $input['source'],
|
||||
'mask' => $input['mask'] ?? null,
|
||||
]);
|
||||
|
||||
/** @var Firewall $firewallHandler */
|
||||
$firewallHandler = $server->firewall()->handler();
|
||||
$firewallHandler->addRule(
|
||||
$rule->type,
|
||||
$rule->getRealProtocol(),
|
||||
$rule->port,
|
||||
$rule->source,
|
||||
$rule->mask
|
||||
);
|
||||
|
||||
$rule->status = FirewallRuleStatus::READY;
|
||||
$rule->save();
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
'in:allow,deny',
|
||||
],
|
||||
'protocol' => [
|
||||
'required',
|
||||
Rule::in(array_keys(config('core.firewall_protocols_port'))),
|
||||
],
|
||||
'port' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:1',
|
||||
'max:65535',
|
||||
],
|
||||
'source' => [
|
||||
'required',
|
||||
'ip',
|
||||
],
|
||||
'mask' => [
|
||||
'nullable',
|
||||
'numeric',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\FirewallRule;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
|
||||
class DeleteRule
|
||||
{
|
||||
public function delete(Server $server, FirewallRule $rule): void
|
||||
{
|
||||
$rule->status = FirewallRuleStatus::DELETING;
|
||||
$rule->save();
|
||||
|
||||
$server->firewall()
|
||||
->handler()
|
||||
->removeRule(
|
||||
$rule->type,
|
||||
$rule->getRealProtocol(),
|
||||
$rule->port,
|
||||
$rule->source,
|
||||
$rule->mask
|
||||
);
|
||||
|
||||
$rule->delete();
|
||||
}
|
||||
}
|
117
app/Actions/FirewallRule/ManageRule.php
Executable file
117
app/Actions/FirewallRule/ManageRule.php
Executable file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\FirewallRule;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\Firewall\Firewall;
|
||||
|
||||
class ManageRule
|
||||
{
|
||||
public function create(Server $server, array $input): FirewallRule
|
||||
{
|
||||
$sourceAny = $input['source_any'] ?? empty($input['source'] ?? null);
|
||||
$rule = new FirewallRule([
|
||||
'name' => $input['name'],
|
||||
'server_id' => $server->id,
|
||||
'type' => $input['type'],
|
||||
'protocol' => $input['protocol'],
|
||||
'port' => $input['port'],
|
||||
'source' => $sourceAny ? null : $input['source'],
|
||||
'mask' => $sourceAny ? null : ($input['mask'] ?? null),
|
||||
'status' => FirewallRuleStatus::CREATING,
|
||||
]);
|
||||
|
||||
$rule->save();
|
||||
|
||||
dispatch(fn () => $this->applyRule($rule));
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public function update(FirewallRule $rule, array $input): FirewallRule
|
||||
{
|
||||
$sourceAny = $input['source_any'] ?? empty($input['source'] ?? null);
|
||||
$rule->update([
|
||||
'name' => $input['name'],
|
||||
'type' => $input['type'],
|
||||
'protocol' => $input['protocol'],
|
||||
'port' => $input['port'],
|
||||
'source' => $sourceAny ? null : $input['source'],
|
||||
'mask' => $sourceAny ? null : ($input['mask'] ?? null),
|
||||
'status' => FirewallRuleStatus::UPDATING,
|
||||
]);
|
||||
|
||||
dispatch(fn () => $this->applyRule($rule));
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public function delete(FirewallRule $rule): void
|
||||
{
|
||||
$rule->status = FirewallRuleStatus::DELETING;
|
||||
$rule->save();
|
||||
|
||||
dispatch(fn () => $this->applyRule($rule));
|
||||
}
|
||||
|
||||
protected function applyRule($rule): void
|
||||
{
|
||||
try {
|
||||
/** @var Firewall $handler */
|
||||
$handler = $rule->server->firewall()->handler();
|
||||
$handler->applyRules();
|
||||
} catch (\Exception $e) {
|
||||
$rule->server->firewallRules()
|
||||
->where('status', '!=', FirewallRuleStatus::READY)
|
||||
->update(['status' => FirewallRuleStatus::FAILED]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($rule->status === FirewallRuleStatus::DELETING) {
|
||||
$rule->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$rule->status = FirewallRuleStatus::READY;
|
||||
$rule->save();
|
||||
}
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:18',
|
||||
],
|
||||
'type' => [
|
||||
'required',
|
||||
'in:allow,deny',
|
||||
],
|
||||
'protocol' => [
|
||||
'required',
|
||||
'in:tcp,udp',
|
||||
],
|
||||
'port' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:1',
|
||||
'max:65535',
|
||||
],
|
||||
'source' => [
|
||||
'nullable',
|
||||
'ip',
|
||||
],
|
||||
'mask' => [
|
||||
'nullable',
|
||||
'numeric',
|
||||
'min:1',
|
||||
'max:32',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -197,26 +197,29 @@ public function createFirewallRules(Server $server): void
|
||||
$server->firewallRules()->createMany([
|
||||
[
|
||||
'type' => 'allow',
|
||||
'protocol' => 'ssh',
|
||||
'name' => 'SSH',
|
||||
'protocol' => 'tcp',
|
||||
'port' => 22,
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => 0,
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
],
|
||||
[
|
||||
'type' => 'allow',
|
||||
'protocol' => 'http',
|
||||
'name' => 'HTTP',
|
||||
'protocol' => 'tcp',
|
||||
'port' => 80,
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => 0,
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
],
|
||||
[
|
||||
'type' => 'allow',
|
||||
'protocol' => 'https',
|
||||
'name' => 'HTTPS',
|
||||
'protocol' => 'tcp',
|
||||
'port' => 443,
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => 0,
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
],
|
||||
]);
|
||||
|
@ -6,7 +6,11 @@ final class FirewallRuleStatus
|
||||
{
|
||||
const CREATING = 'creating';
|
||||
|
||||
const UPDATING = 'updating';
|
||||
|
||||
const READY = 'ready';
|
||||
|
||||
const DELETING = 'deleting';
|
||||
|
||||
const FAILED = 'failed';
|
||||
}
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Actions\FirewallRule\CreateRule;
|
||||
use App\Actions\FirewallRule\DeleteRule;
|
||||
use App\Actions\FirewallRule\ManageRule;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\FirewallRuleResource;
|
||||
use App\Models\FirewallRule;
|
||||
@ -21,6 +20,7 @@
|
||||
use Spatie\RouteAttributes\Attributes\Middleware;
|
||||
use Spatie\RouteAttributes\Attributes\Post;
|
||||
use Spatie\RouteAttributes\Attributes\Prefix;
|
||||
use Spatie\RouteAttributes\Attributes\Put;
|
||||
|
||||
#[Prefix('api/projects/{project}/servers/{server}/firewall-rules')]
|
||||
#[Middleware(['auth:sanctum', 'can-see-project'])]
|
||||
@ -41,10 +41,11 @@ public function index(Project $project, Server $server): ResourceCollection
|
||||
|
||||
#[Post('/', name: 'api.projects.servers.firewall-rules.create', middleware: 'ability:write')]
|
||||
#[Endpoint(title: 'create', description: 'Create a new firewall rule.')]
|
||||
#[BodyParam(name: 'name', required: true)]
|
||||
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
|
||||
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
|
||||
#[BodyParam(name: 'port', required: true)]
|
||||
#[BodyParam(name: 'source', required: true)]
|
||||
#[BodyParam(name: 'source', required: false)]
|
||||
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
|
||||
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
|
||||
public function create(Request $request, Project $project, Server $server): FirewallRuleResource
|
||||
@ -53,9 +54,31 @@ public function create(Request $request, Project $project, Server $server): Fire
|
||||
|
||||
$this->validateRoute($project, $server);
|
||||
|
||||
$this->validate($request, CreateRule::rules());
|
||||
$this->validate($request, ManageRule::rules());
|
||||
|
||||
$firewallRule = app(CreateRule::class)->create($server, $request->all());
|
||||
$firewallRule = app(ManageRule::class)->create($server, $request->all());
|
||||
|
||||
return new FirewallRuleResource($firewallRule);
|
||||
}
|
||||
|
||||
#[Put('{firewallRule}', name: 'api.projects.servers.firewall-rules.edit', middleware: 'ability:write')]
|
||||
#[Endpoint(title: 'edit', description: 'Update an existing firewall rule.')]
|
||||
#[BodyParam(name: 'name', required: true)]
|
||||
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
|
||||
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
|
||||
#[BodyParam(name: 'port', required: true)]
|
||||
#[BodyParam(name: 'source', required: false)]
|
||||
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
|
||||
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
|
||||
public function edit(Request $request, Project $project, Server $server, FirewallRule $firewallRule): FirewallRuleResource
|
||||
{
|
||||
$this->authorize('update', [FirewallRule::class, $firewallRule]);
|
||||
|
||||
$this->validateRoute($project, $server);
|
||||
|
||||
$this->validate($request, ManageRule::rules());
|
||||
|
||||
$firewallRule = app(ManageRule::class)->update($firewallRule, $request->all());
|
||||
|
||||
return new FirewallRuleResource($firewallRule);
|
||||
}
|
||||
@ -81,7 +104,7 @@ public function delete(Project $project, Server $server, FirewallRule $firewallR
|
||||
|
||||
$this->validateRoute($project, $server, $firewallRule);
|
||||
|
||||
app(DeleteRule::class)->delete($server, $firewallRule);
|
||||
app(ManageRule::class)->delete($firewallRule);
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'server_id' => $this->server_id,
|
||||
'type' => $this->type,
|
||||
'protocol' => $this->protocol,
|
||||
|
@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $server_id
|
||||
* @property string $name
|
||||
* @property string $type
|
||||
* @property string $protocol
|
||||
* @property int $port
|
||||
@ -21,6 +23,7 @@ class FirewallRule extends AbstractModel
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'server_id',
|
||||
'type',
|
||||
'protocol',
|
||||
@ -36,13 +39,19 @@ class FirewallRule extends AbstractModel
|
||||
'port' => 'integer',
|
||||
];
|
||||
|
||||
public function getStatusColor(): string
|
||||
{
|
||||
return match ($this->status) {
|
||||
FirewallRuleStatus::CREATING,
|
||||
FirewallRuleStatus::UPDATING,
|
||||
FirewallRuleStatus::DELETING => 'warning',
|
||||
FirewallRuleStatus::READY => 'success',
|
||||
FirewallRuleStatus::FAILED => 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function getRealProtocol(): string
|
||||
{
|
||||
return $this->protocol === 'udp' ? 'udp' : 'tcp';
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,5 @@
|
||||
|
||||
interface Firewall
|
||||
{
|
||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void;
|
||||
|
||||
public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void;
|
||||
public function applyRules(): void;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\SSH\Services\Firewall;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Exceptions\SSHError;
|
||||
|
||||
class Ufw extends AbstractFirewall
|
||||
@ -26,34 +27,16 @@ public function uninstall(): void
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
||||
public function applyRules(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.firewall.ufw.add-rule', [
|
||||
'type' => $type,
|
||||
'protocol' => $protocol,
|
||||
'port' => $port,
|
||||
'source' => $source,
|
||||
'mask' => $mask || $mask === 0 ? '/'.$mask : '',
|
||||
]),
|
||||
'add-firewall-rule'
|
||||
);
|
||||
}
|
||||
$rules = $this->service->server
|
||||
->firewallRules()
|
||||
->where('status', '!=', FirewallRuleStatus::DELETING)
|
||||
->get();
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.firewall.ufw.remove-rule', [
|
||||
'type' => $type,
|
||||
'protocol' => $protocol,
|
||||
'port' => $port,
|
||||
'source' => $source,
|
||||
'mask' => $mask || $mask === 0 ? '/'.$mask : '',
|
||||
]),
|
||||
'remove-firewall-rule'
|
||||
view('ssh.services.firewall.ufw.apply-rules', compact('rules')),
|
||||
'apply-rules'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,17 @@
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall;
|
||||
|
||||
use App\Actions\FirewallRule\CreateRule;
|
||||
use App\Actions\FirewallRule\ManageRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Web\Pages\Servers\Page;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
@ -31,6 +34,64 @@ public function getWidgets(): array
|
||||
];
|
||||
}
|
||||
|
||||
public static function getFirewallForm(?FirewallRule $record = null): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('name')
|
||||
->label('Purpose')
|
||||
->default($record->name ?? null)
|
||||
->rules(ManageRule::rules()['name']),
|
||||
Select::make('type')
|
||||
->label('Type')
|
||||
->default($record->type ?? 'allow')
|
||||
->options([
|
||||
'allow' => 'Allow',
|
||||
'deny' => 'Deny',
|
||||
])
|
||||
->rules(ManageRule::rules()['type']),
|
||||
Select::make('protocol')
|
||||
->label('Protocol')
|
||||
->default($record->protocol ?? 'tcp')
|
||||
->options([
|
||||
'tcp' => 'TCP',
|
||||
'udp' => 'UDP',
|
||||
])
|
||||
->rules(ManageRule::rules()['protocol']),
|
||||
TextInput::make('port')
|
||||
->label('Port')
|
||||
->default($record->port ?? null)
|
||||
->rules(['required', 'integer']),
|
||||
Checkbox::make('source_any')
|
||||
->label('Any Source')
|
||||
->default(($record->source ?? null) == null)
|
||||
->rules(['boolean'])
|
||||
->helperText('Allow connections from any source, regardless of their IP address or subnet mask.')
|
||||
->live(),
|
||||
TextInput::make('source')
|
||||
->hidden(fn (Get $get) => $get('source_any') == true)
|
||||
->label('Source')
|
||||
->helperText('The IP address of the source of the connection.')
|
||||
->rules(ManageRule::rules()['source'])
|
||||
->default($record->source ?? null)
|
||||
->suffixAction(
|
||||
\Filament\Forms\Components\Actions\Action::make('get_ip')
|
||||
->icon('heroicon-o-globe-alt')
|
||||
->color('primary')
|
||||
->tooltip('Use My IP')
|
||||
->action(function ($set) {
|
||||
$ip = Request::ip();
|
||||
$set('source', $ip);
|
||||
})
|
||||
),
|
||||
TextInput::make('mask')
|
||||
->hidden(fn (Get $get) => $get('source_any') == true)
|
||||
->label('Mask')
|
||||
->default($record->mask ?? null)
|
||||
->helperText('The subnet mask of the source of the connection. Leave blank for a single IP address.')
|
||||
->rules(ManageRule::rules()['mask']),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
@ -45,37 +106,19 @@ protected function getHeaderActions(): array
|
||||
->label('Create a Rule')
|
||||
->icon('heroicon-o-plus')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->form([
|
||||
Select::make('type')
|
||||
->native(false)
|
||||
->options([
|
||||
'allow' => 'Allow',
|
||||
'deny' => 'Deny',
|
||||
])
|
||||
->rules(CreateRule::rules()['type']),
|
||||
Select::make('protocol')
|
||||
->native(false)
|
||||
->options([
|
||||
'tcp' => 'TCP',
|
||||
'udp' => 'UDP',
|
||||
])
|
||||
->rules(CreateRule::rules()['protocol']),
|
||||
TextInput::make('port')
|
||||
->rules(CreateRule::rules()['port']),
|
||||
TextInput::make('source')
|
||||
->rules(CreateRule::rules()['source']),
|
||||
TextInput::make('mask')
|
||||
->rules(CreateRule::rules()['mask']),
|
||||
])
|
||||
->modalHeading('Create Firewall Rule')
|
||||
->modalDescription('Add a new rule to the firewall')
|
||||
->modalSubmitActionLabel('Create')
|
||||
->form(self::getFirewallForm())
|
||||
->action(function (array $data) {
|
||||
run_action($this, function () use ($data) {
|
||||
app(CreateRule::class)->create($this->server, $data);
|
||||
app(ManageRule::class)->create($this->server, $data);
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Firewall rule created!')
|
||||
->title('Applying Firewall Rule')
|
||||
->send();
|
||||
});
|
||||
}),
|
||||
|
@ -2,15 +2,18 @@
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall\Widgets;
|
||||
|
||||
use App\Actions\FirewallRule\DeleteRule;
|
||||
use App\Actions\FirewallRule\ManageRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\Web\Pages\Servers\Firewall\Index;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RulesList extends Widget
|
||||
{
|
||||
@ -26,19 +29,40 @@ protected function getTableQuery(): Builder
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->label('Purpose'),
|
||||
TextColumn::make('type')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase'])
|
||||
->color(fn (FirewallRule $record) => $record->type === 'allow' ? 'green' : 'red'),
|
||||
->badge()
|
||||
->color(fn ($state) => $state === 'allow' ? 'success' : 'warning')
|
||||
->label('Type')
|
||||
->formatStateUsing(fn ($state) => Str::upper($state)),
|
||||
TextColumn::make('id')
|
||||
->sortable()
|
||||
->label('Source')
|
||||
->formatStateUsing(function (FirewallRule $record) {
|
||||
$source = $record->source == null ? 'any' : $record->source;
|
||||
if ($source !== 'any' && $record->mask !== null) {
|
||||
$source .= '/'.$record->mask;
|
||||
}
|
||||
|
||||
return $source;
|
||||
}),
|
||||
TextColumn::make('protocol')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase']),
|
||||
->badge()
|
||||
->color('primary')
|
||||
->label('Protocol')
|
||||
->formatStateUsing(fn ($state) => Str::upper($state)),
|
||||
TextColumn::make('port')
|
||||
->sortable(),
|
||||
TextColumn::make('source')
|
||||
->sortable(),
|
||||
TextColumn::make('mask')
|
||||
->sortable(),
|
||||
->sortable()
|
||||
->label('Port'),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->color(fn (FirewallRule $record) => $record->getStatusColor()),
|
||||
];
|
||||
}
|
||||
|
||||
@ -49,6 +73,28 @@ public function table(Table $table): Table
|
||||
->query($this->getTableQuery())
|
||||
->columns($this->getTableColumns())
|
||||
->actions([
|
||||
Action::make('edit')
|
||||
->icon('heroicon-o-pencil')
|
||||
->tooltip('Edit')
|
||||
->hiddenLabel()
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->modalHeading('Edit Firewall Rule')
|
||||
->modalDescription('Edit the associated servers firewall rule.')
|
||||
->modalSubmitActionLabel('Update')
|
||||
->authorize(fn (FirewallRule $record) => auth()->user()->can('update', $record))
|
||||
->form(fn ($record) => Index::getFirewallForm($record))
|
||||
->action(function (FirewallRule $record, array $data) {
|
||||
run_action($this, function () use ($record, $data) {
|
||||
app(ManageRule::class)->update($record, $data);
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Applying Firewall Rule')
|
||||
->send();
|
||||
});
|
||||
}),
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
@ -58,7 +104,7 @@ public function table(Table $table): Table
|
||||
->authorize(fn (FirewallRule $record) => auth()->user()->can('delete', $record))
|
||||
->action(function (FirewallRule $record) {
|
||||
try {
|
||||
app(DeleteRule::class)->delete($this->server, $record);
|
||||
app(ManageRule::class)->delete($record);
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
|
@ -485,14 +485,6 @@
|
||||
'post_max_size' => 'M',
|
||||
],
|
||||
|
||||
/*
|
||||
* firewall
|
||||
*/
|
||||
'firewall_protocols_port' => [
|
||||
'tcp' => '',
|
||||
'udp' => '',
|
||||
],
|
||||
|
||||
/*
|
||||
* Disable these IPs for servers
|
||||
*/
|
||||
|
@ -12,6 +12,7 @@ class FirewallRuleFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->word,
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => $this->faker->numberBetween(1, 65535),
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?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('firewall_rules', function (Blueprint $table) {
|
||||
$table->string('name')->default('Undefined')->after('id');
|
||||
$table->ipAddress('source')->default(null)->nullable()->change();
|
||||
});
|
||||
|
||||
DB::statement("UPDATE firewall_rules SET name = UPPER(protocol) WHERE protocol IN ('ssh', 'http', 'https')");
|
||||
DB::statement("UPDATE firewall_rules SET protocol = 'tcp' WHERE protocol IN ('ssh', 'http', 'https')");
|
||||
DB::statement("UPDATE firewall_rules SET source = null WHERE source = '0.0.0.0'");
|
||||
DB::statement("UPDATE firewall_rules SET mask = null WHERE mask = '0'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement("UPDATE firewall_rules SET protocol = LOWER(name) WHERE protocol = 'tcp' AND LOWER(name) IN ('ssh', 'http', 'https')");
|
||||
DB::statement("UPDATE firewall_rules SET source = '0.0.0.0' WHERE source is null");
|
||||
DB::statement("UPDATE firewall_rules SET mask = '0' WHERE mask is null");
|
||||
|
||||
Schema::table('firewall_rules', function (Blueprint $table) {
|
||||
$table->dropColumn('name');
|
||||
$table->ipAddress('source')->default('0.0.0.0')->change();
|
||||
});
|
||||
}
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
if ! sudo ufw {{ $type }} from {{ $source }}{{ $mask }} to any proto {{ $protocol }} port {{ $port }}; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw reload; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo service ufw restart; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -0,0 +1,37 @@
|
||||
@include('ssh.services.firewall.ufw.backup-rules')
|
||||
|
||||
if ! sudo ufw --force reset; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
||||
if ! sudo ufw default deny incoming; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw default allow outgoing; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
@foreach($rules as $rule)
|
||||
@php
|
||||
$source = isset($rule->source) && $rule->source !== null
|
||||
? $rule->source . (isset($rule->mask) && $rule->mask !== null ? '/' . $rule->mask : '')
|
||||
: 'any';
|
||||
@endphp
|
||||
|
||||
if ! sudo ufw {{ $rule->type }} from {{ $source }} to any proto {{ $rule->protocol }} port {{ $rule->port }}; then
|
||||
@include('ssh.services.firewall.ufw.restore-rules')
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
@endforeach
|
||||
|
||||
if ! sudo ufw --force enable; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw reload; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
@include('ssh.services.firewall.ufw.clear-backups')
|
@ -0,0 +1,6 @@
|
||||
sudo cp /etc/ufw/before.rules /tmp/ufw.before.backup
|
||||
sudo cp /etc/ufw/after.rules /tmp/ufw.after.backup
|
||||
sudo cp /etc/ufw/user.rules /tmp/ufw.user.backup
|
||||
sudo cp /etc/ufw/before6.rules /tmp/ufw.before6.backup
|
||||
sudo cp /etc/ufw/after6.rules /tmp/ufw.after6.backup
|
||||
sudo cp /etc/ufw/user6.rules /tmp/ufw.user6.backup
|
@ -0,0 +1,6 @@
|
||||
sudo rm -f /tmp/ufw.before.backup
|
||||
sudo rm -f /tmp/ufw.after.backup
|
||||
sudo rm -f /tmp/ufw.user.backup
|
||||
sudo rm -f /tmp/ufw.before6.backup
|
||||
sudo rm -f /tmp/ufw.after6.backup
|
||||
sudo rm -f /tmp/ufw.user6.backup
|
@ -1,11 +0,0 @@
|
||||
if ! sudo ufw delete {{ $type }} from {{ $source }}{{ $mask }} to any proto {{ $protocol }} port {{ $port }}; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw reload; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo service ufw restart; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -0,0 +1,10 @@
|
||||
sudo ufw --force disable
|
||||
|
||||
sudo cp /tmp/ufw.before.backup /etc/ufw/before.rules
|
||||
sudo cp /tmp/ufw.after.backup /etc/ufw/after.rules
|
||||
sudo cp /tmp/ufw.user.backup /etc/ufw/user.rules
|
||||
sudo cp /tmp/ufw.before6.backup /etc/ufw/before6.rules
|
||||
sudo cp /tmp/ufw.after6.backup /etc/ufw/after6.rules
|
||||
sudo cp /tmp/ufw.user6.backup /etc/ufw/user6.rules
|
||||
|
||||
sudo ufw --force enable
|
@ -23,16 +23,47 @@ public function test_create_firewall_rule(): void
|
||||
'project' => $this->server->project,
|
||||
'server' => $this->server,
|
||||
]), [
|
||||
'name' => 'Test',
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => '1234',
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => '0',
|
||||
'mask' => '1',
|
||||
])
|
||||
->assertSuccessful()
|
||||
->assertJsonFragment([
|
||||
'port' => 1234,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
'status' => FirewallRuleStatus::CREATING,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_edit_firewall_rule(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
Sanctum::actingAs($this->user, ['read', 'write']);
|
||||
|
||||
$rule = FirewallRule::factory()->create([
|
||||
'server_id' => $this->server->id,
|
||||
'port' => 1234,
|
||||
]);
|
||||
|
||||
$this->json('PUT', route('api.projects.servers.firewall-rules.edit', [
|
||||
'project' => $this->server->project,
|
||||
'server' => $this->server,
|
||||
'firewallRule' => $rule,
|
||||
]), [
|
||||
'name' => 'Test',
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => '55',
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
])
|
||||
->assertSuccessful()
|
||||
->assertJsonFragment([
|
||||
'port' => 55,
|
||||
'status' => FirewallRuleStatus::UPDATING,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ public function test_create_firewall_rule(): void
|
||||
'server' => $this->server,
|
||||
])
|
||||
->callAction('create', [
|
||||
'name' => 'Test',
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => '1234',
|
||||
|
Loading…
x
Reference in New Issue
Block a user