Refactor firewall and add edit rule (#488)

This commit is contained in:
Richard Anderson 2025-02-16 19:31:58 +00:00 committed by GitHub
parent e2b9d18a71
commit 8c7c3d2192
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 443 additions and 210 deletions

View File

@ -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',
],
];
}
}

View File

@ -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();
}
}

View 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',
],
];
}
}

View File

@ -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,
],
]);

View File

@ -6,7 +6,11 @@ final class FirewallRuleStatus
{
const CREATING = 'creating';
const UPDATING = 'updating';
const READY = 'ready';
const DELETING = 'deleting';
const FAILED = 'failed';
}

View File

@ -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();
}

View File

@ -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,

View File

@ -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';
}
}

View File

@ -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;
}

View File

@ -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'
);
}
}

View File

@ -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();
});
}),

View File

@ -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()

View File

@ -485,14 +485,6 @@
'post_max_size' => 'M',
],
/*
* firewall
*/
'firewall_protocols_port' => [
'tcp' => '',
'udp' => '',
],
/*
* Disable these IPs for servers
*/

View File

@ -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),

View File

@ -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();
});
}
};

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,
]);
}

View File

@ -25,6 +25,7 @@ public function test_create_firewall_rule(): void
'server' => $this->server,
])
->callAction('create', [
'name' => 'Test',
'type' => 'allow',
'protocol' => 'tcp',
'port' => '1234',