88 lines
3.2 KiB
PHP
88 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Support\Os;
|
|
|
|
/**
|
|
* Shell command strings for the host firewall (ufw or firewalld), built per OS.
|
|
*
|
|
* Safety model is preserved across both tools: enableScript() ALWAYS opens the
|
|
* real sshd port + 80/443 BEFORE turning the firewall on, so Clusev can never sever
|
|
* its own SSH session. Disable is the clean stop — never `firewall-cmd --panic-on`
|
|
* (which would drop ALL traffic, the exact lockout the guard avoids).
|
|
*/
|
|
final class FirewallTool
|
|
{
|
|
/** @param 'ufw'|'firewalld' $tool */
|
|
public function __construct(
|
|
private string $tool,
|
|
private PackageManager $pm,
|
|
private bool $canInstall,
|
|
) {}
|
|
|
|
public static function for(OsProfile $os): self
|
|
{
|
|
return new self(
|
|
$os->firewallTool === 'firewalld' ? 'firewalld' : 'ufw',
|
|
$os->managesPackages() ? PackageManager::for($os) : new PackageManager('none'),
|
|
$os->managesPackages(),
|
|
);
|
|
}
|
|
|
|
public function isFirewalld(): bool
|
|
{
|
|
return $this->tool === 'firewalld';
|
|
}
|
|
|
|
private function packageName(): string
|
|
{
|
|
return $this->tool === 'firewalld' ? 'firewalld' : 'ufw';
|
|
}
|
|
|
|
/** Guarded enable: install if needed + missing, open ssh/80/443, then turn on. */
|
|
public function enableScript(int $sshPort): string
|
|
{
|
|
$ensure = $this->canInstall ? $this->pm->ensureInstalledScript($this->packageName()).' && ' : '';
|
|
|
|
if ($this->isFirewalld()) {
|
|
$ports = "--add-port={$sshPort}/tcp --add-port=80/tcp --add-port=443/tcp";
|
|
|
|
// GUARD: open ssh/80/443 in the PERMANENT config BEFORE firewalld starts
|
|
// filtering, so starting it can never drop the operator's SSH session (a
|
|
// custom SSH port is not in the default zone). Handle both states: if the
|
|
// daemon is already running, add to permanent + reload; if it is stopped,
|
|
// write the permanent rules offline first, THEN start it.
|
|
return $ensure
|
|
.'if systemctl is-active --quiet firewalld; then '
|
|
."firewall-cmd --permanent {$ports} && systemctl enable --now firewalld && firewall-cmd --reload; "
|
|
.'else '
|
|
."firewall-offline-cmd {$ports} && systemctl enable --now firewalld; "
|
|
.'fi';
|
|
}
|
|
|
|
return $ensure
|
|
."ufw allow {$sshPort}/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw --force enable";
|
|
}
|
|
|
|
/** Clean disable (never a panic/drop-all). */
|
|
public function disableScript(): string
|
|
{
|
|
return $this->isFirewalld() ? 'systemctl disable --now firewalld' : 'ufw disable';
|
|
}
|
|
|
|
/** Shell CONDITION exiting 0 when the firewall binary is present. */
|
|
public function installedTest(): string
|
|
{
|
|
return $this->isFirewalld()
|
|
? 'command -v firewall-cmd >/dev/null 2>&1'
|
|
: 'command -v ufw >/dev/null 2>&1';
|
|
}
|
|
|
|
/** Shell CONDITION exiting 0 when the firewall is currently active. */
|
|
public function activeTest(): string
|
|
{
|
|
return $this->isFirewalld()
|
|
? 'systemctl is-active --quiet firewalld'
|
|
: 'ufw status 2>/dev/null | grep -qi "Status: active"';
|
|
}
|
|
}
|