refactor(firewall): separate enforcement from rule management
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXC\Service;
|
||||
|
||||
use KTXC\Models\Firewall\FirewallRuleObject;
|
||||
use KTXC\Stores\FirewallStore;
|
||||
use KTXF\Event\EventDispatcherInterface;
|
||||
use KTXF\Event\SecurityEvent;
|
||||
|
||||
final class FirewallRuleManager
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FirewallStore $store,
|
||||
private readonly FirewallRuleCache $cache,
|
||||
private readonly EventDispatcherInterface $events,
|
||||
) {
|
||||
}
|
||||
|
||||
public function list(FirewallRuleScope $scope, bool $activeOnly = true): array
|
||||
{
|
||||
return $scope->scope === FirewallRuleObject::SCOPE_SYSTEM
|
||||
? $this->store->listSystemRules($activeOnly)
|
||||
: $this->store->listRules($scope->tenantId, $activeOnly);
|
||||
}
|
||||
|
||||
public function blockIp(
|
||||
FirewallRuleScope $scope,
|
||||
string $ipAddress,
|
||||
?string $reason,
|
||||
?string $createdBy,
|
||||
?int $durationSeconds = null
|
||||
): FirewallRuleObject {
|
||||
$ipAddress = FirewallRuleValidator::ipAddress($ipAddress);
|
||||
FirewallRuleValidator::duration($durationSeconds);
|
||||
|
||||
$existing = $this->store->findExactIpRule(
|
||||
$scope->tenantId,
|
||||
$ipAddress,
|
||||
FirewallRuleObject::ACTION_BLOCK,
|
||||
$scope->scope
|
||||
);
|
||||
if ($existing) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$rule = $this->create(
|
||||
$scope,
|
||||
FirewallRuleObject::TYPE_IP,
|
||||
FirewallRuleObject::ACTION_BLOCK,
|
||||
$ipAddress,
|
||||
$reason ?? 'Blocked by administrator',
|
||||
$createdBy,
|
||||
$durationSeconds
|
||||
);
|
||||
$this->publishIpEvent(SecurityEvent::IP_BLOCKED, $scope, $ipAddress, $reason);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public function allowIp(
|
||||
FirewallRuleScope $scope,
|
||||
string $ipAddress,
|
||||
?string $reason,
|
||||
?string $createdBy
|
||||
): FirewallRuleObject {
|
||||
$ipAddress = FirewallRuleValidator::ipAddress($ipAddress);
|
||||
$rule = $this->create(
|
||||
$scope,
|
||||
FirewallRuleObject::TYPE_IP,
|
||||
FirewallRuleObject::ACTION_ALLOW,
|
||||
$ipAddress,
|
||||
$reason ?? 'Allowed by administrator',
|
||||
$createdBy
|
||||
);
|
||||
$this->publishIpEvent(SecurityEvent::IP_ALLOWED, $scope, $ipAddress, $reason);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public function blockIpRange(
|
||||
FirewallRuleScope $scope,
|
||||
string $cidr,
|
||||
?string $reason,
|
||||
?string $createdBy
|
||||
): FirewallRuleObject {
|
||||
return $this->create(
|
||||
$scope,
|
||||
FirewallRuleObject::TYPE_IP_RANGE,
|
||||
FirewallRuleObject::ACTION_BLOCK,
|
||||
FirewallRuleValidator::cidr($cidr),
|
||||
$reason ?? 'Range blocked by administrator',
|
||||
$createdBy
|
||||
);
|
||||
}
|
||||
|
||||
public function blockDevice(
|
||||
FirewallRuleScope $scope,
|
||||
string $fingerprint,
|
||||
?string $reason,
|
||||
?string $createdBy,
|
||||
?int $durationSeconds = null
|
||||
): FirewallRuleObject {
|
||||
FirewallRuleValidator::duration($durationSeconds);
|
||||
$fingerprint = FirewallRuleValidator::deviceFingerprint($fingerprint);
|
||||
$rule = $this->create(
|
||||
$scope,
|
||||
FirewallRuleObject::TYPE_DEVICE,
|
||||
FirewallRuleObject::ACTION_BLOCK,
|
||||
$fingerprint,
|
||||
$reason ?? 'Device blocked by administrator',
|
||||
$createdBy,
|
||||
$durationSeconds
|
||||
);
|
||||
|
||||
$event = new SecurityEvent(SecurityEvent::DEVICE_BLOCKED, ['device' => $fingerprint, 'reason' => $reason]);
|
||||
$event->setDeviceFingerprint($fingerprint)->setReason($reason)->setTenantId($scope->tenantId);
|
||||
$this->events->dispatch($event);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public function disable(FirewallRuleScope $scope, string $ruleId): bool
|
||||
{
|
||||
$rule = $this->ownedRule($scope, $ruleId);
|
||||
if (!$rule) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rule->setEnabled(false);
|
||||
$this->store->depositRule($rule);
|
||||
$this->cache->invalidate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function remove(FirewallRuleScope $scope, string $ruleId): bool
|
||||
{
|
||||
$rule = $this->ownedRule($scope, $ruleId);
|
||||
if (!$rule) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->store->destroyRule($rule);
|
||||
$this->cache->invalidate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function create(
|
||||
FirewallRuleScope $scope,
|
||||
string $type,
|
||||
string $action,
|
||||
string $value,
|
||||
string $reason,
|
||||
?string $createdBy,
|
||||
?int $durationSeconds = null
|
||||
): FirewallRuleObject {
|
||||
$rule = (new FirewallRuleObject())
|
||||
->setScope($scope->scope)
|
||||
->setTenantId($scope->tenantId)
|
||||
->setType($type)
|
||||
->setAction($action)
|
||||
->setValue($value)
|
||||
->setReason($reason)
|
||||
->setCreatedBy($createdBy)
|
||||
->setCreatedAt(new \DateTimeImmutable())
|
||||
->setEnabled(true);
|
||||
|
||||
if ($durationSeconds !== null) {
|
||||
$rule->setExpiresAt((new \DateTimeImmutable())->modify("+{$durationSeconds} seconds"));
|
||||
}
|
||||
|
||||
$this->store->depositRule($rule);
|
||||
$this->cache->invalidate();
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
private function ownedRule(FirewallRuleScope $scope, string $ruleId): ?FirewallRuleObject
|
||||
{
|
||||
$rule = $this->store->fetchRule($ruleId);
|
||||
|
||||
return $rule && $scope->owns($rule) ? $rule : null;
|
||||
}
|
||||
|
||||
private function publishIpEvent(
|
||||
string $name,
|
||||
FirewallRuleScope $scope,
|
||||
string $ipAddress,
|
||||
?string $reason
|
||||
): void {
|
||||
$event = new SecurityEvent($name, ['ip' => $ipAddress, 'reason' => $reason]);
|
||||
$event->setIpAddress($ipAddress)->setReason($reason)->setTenantId($scope->tenantId);
|
||||
$this->events->dispatch($event);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user