Fix ModSecurity install/setup and bump version

This commit is contained in:
root
2026-01-29 03:44:57 +02:00
parent 884b3b27a8
commit b1aa58fdca
5 changed files with 154 additions and 20 deletions

View File

@@ -5,7 +5,7 @@
A modern web hosting control panel for WordPress and general PHP hosting. Built with Laravel 12, Filament v5, Livewire 4, and Tailwind CSS v4. A modern web hosting control panel for WordPress and general PHP hosting. Built with Laravel 12, Filament v5, Livewire 4, and Tailwind CSS v4.
Version: 0.9-rc23 (release candidate) Version: 0.9-rc24 (release candidate)
This is a release candidate. Expect rapid iteration and breaking changes until 1.0. This is a release candidate. Expect rapid iteration and breaking changes until 1.0.
@@ -156,6 +156,7 @@ php artisan test --compact
## Initial Release ## Initial Release
- 0.9-rc24: WAF installer improvements and ModSecurity setup fixes.
- 0.9-rc: initial release candidate with core hosting, mail, DNS, SSL, backups, and migrations. - 0.9-rc: initial release candidate with core hosting, mail, DNS, SSL, backups, and migrations.
## License ## License

View File

@@ -1 +1 @@
VERSION=0.9-rc23 VERSION=0.9-rc24

View File

@@ -48,11 +48,10 @@ class UpgradeCommand extends Command
try { try {
$this->configureGitSafeDirectory(); $this->configureGitSafeDirectory();
$this->ensureGitRepository(); $this->ensureGitRepository();
// Fetch from remote without merging $updateSource = $this->fetchUpdates();
$this->executeCommandOrFail('git fetch origin main');
// Check if there are updates // Check if there are updates
$behindCount = trim($this->executeCommandOrFail('git rev-list HEAD..origin/main --count')); $behindCount = trim($this->executeCommandOrFail("git rev-list HEAD..{$updateSource['remoteRef']} --count"));
if ($behindCount === '0') { if ($behindCount === '0') {
$this->info('Jabali Panel is up to date!'); $this->info('Jabali Panel is up to date!');
@@ -64,7 +63,7 @@ class UpgradeCommand extends Command
// Show recent commits // Show recent commits
$this->line("\nRecent changes:"); $this->line("\nRecent changes:");
$commits = $this->executeCommandOrFail('git log HEAD..origin/main --oneline -10'); $commits = $this->executeCommandOrFail("git log HEAD..{$updateSource['remoteRef']} --oneline -10");
if ($commits !== '') { if ($commits !== '') {
$this->line($commits); $this->line($commits);
} }
@@ -115,7 +114,7 @@ class UpgradeCommand extends Command
// Step 2: Fetch updates // Step 2: Fetch updates
$this->info('[2/9] Fetching updates from repository...'); $this->info('[2/9] Fetching updates from repository...');
try { try {
$this->executeCommandOrFail('git fetch origin main'); $updateSource = $this->fetchUpdates();
} catch (Exception $e) { } catch (Exception $e) {
$this->error('Failed to fetch updates: '.$e->getMessage()); $this->error('Failed to fetch updates: '.$e->getMessage());
@@ -123,7 +122,7 @@ class UpgradeCommand extends Command
} }
// Step 3: Check if updates available // Step 3: Check if updates available
$behindCount = trim($this->executeCommandOrFail('git rev-list HEAD..origin/main --count')); $behindCount = trim($this->executeCommandOrFail("git rev-list HEAD..{$updateSource['remoteRef']} --count"));
if ($behindCount === '0' && ! $this->option('force')) { if ($behindCount === '0' && ! $this->option('force')) {
$this->info('Already up to date!'); $this->info('Already up to date!');
@@ -134,7 +133,7 @@ class UpgradeCommand extends Command
$oldHead = trim($this->executeCommandOrFail('git rev-parse HEAD')); $oldHead = trim($this->executeCommandOrFail('git rev-parse HEAD'));
$this->info('[3/9] Pulling latest changes...'); $this->info('[3/9] Pulling latest changes...');
try { try {
$pullResult = $this->executeCommand('git pull --ff-only origin main'); $pullResult = $this->executeCommand("git pull --ff-only {$updateSource['pullRemote']} main");
if ($pullResult['exitCode'] !== 0) { if ($pullResult['exitCode'] !== 0) {
throw new Exception($pullResult['output'] ?: 'Git pull failed.'); throw new Exception($pullResult['output'] ?: 'Git pull failed.');
} }
@@ -249,6 +248,76 @@ class UpgradeCommand extends Command
return 0; return 0;
} }
/**
* @return array{pullRemote: string, remoteRef: string}
*/
private function fetchUpdates(): array
{
$originUrl = trim($this->executeCommandOrFail('git remote get-url origin'));
$fetchAttempts = [
['remote' => 'origin', 'ref' => 'origin/main', 'type' => 'remote'],
];
if ($this->hasRemote('gitea')) {
$fetchAttempts[] = ['remote' => 'gitea', 'ref' => 'gitea/main', 'type' => 'remote'];
}
if ($this->isGithubSshUrl($originUrl)) {
$fetchAttempts[] = [
'remote' => $this->githubHttpsUrlFromSsh($originUrl),
'ref' => 'jabali-upgrade/main',
'type' => 'url',
];
}
$lastError = null;
foreach ($fetchAttempts as $attempt) {
$result = $attempt['type'] === 'url'
? $this->executeCommand("git fetch {$attempt['remote']} main:refs/remotes/{$attempt['ref']}")
: $this->executeCommand("git fetch {$attempt['remote']} main");
if (($result['exitCode'] ?? 1) === 0) {
return [
'pullRemote' => $attempt['remote'],
'remoteRef' => $attempt['ref'],
];
}
$lastError = $result['output'] ?? 'Unknown error';
}
throw new Exception($lastError ?: 'Unable to fetch updates from any configured remote.');
}
private function hasRemote(string $remote): bool
{
$result = $this->executeCommand("git remote get-url {$remote}");
return ($result['exitCode'] ?? 1) === 0;
}
private function isGithubSshUrl(string $url): bool
{
return str_starts_with($url, 'git@github.com:') || str_starts_with($url, 'ssh://git@github.com/');
}
private function githubHttpsUrlFromSsh(string $url): string
{
if (str_starts_with($url, 'git@github.com:')) {
$path = substr($url, strlen('git@github.com:'));
return 'https://github.com/'.$path;
}
if (str_starts_with($url, 'ssh://git@github.com/')) {
$path = substr($url, strlen('ssh://git@github.com/'));
return 'https://github.com/'.$path;
}
return $url;
}
private function getCurrentVersion(): string private function getCurrentVersion(): string
{ {
if (! File::exists($this->versionFile)) { if (! File::exists($this->versionFile)) {

View File

@@ -2781,8 +2781,20 @@ function ensureJabaliNginxIncludeFiles(): void
@mkdir(JABALI_NGINX_INCLUDES, 0755, true); @mkdir(JABALI_NGINX_INCLUDES, 0755, true);
} }
$baseConfig = findWafBaseConfig();
$shouldDisableWaf = $baseConfig === null;
if (!file_exists(JABALI_WAF_INCLUDE)) { if (!file_exists(JABALI_WAF_INCLUDE)) {
file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\n"); $content = "# Managed by Jabali\n";
if ($shouldDisableWaf) {
$content .= "modsecurity off;\n";
}
file_put_contents(JABALI_WAF_INCLUDE, $content);
} elseif ($shouldDisableWaf) {
$current = file_get_contents(JABALI_WAF_INCLUDE);
if ($current === false || strpos($current, 'modsecurity_rules_file') !== false || strpos($current, 'modsecurity on;') !== false) {
file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n");
}
} }
if (!file_exists(JABALI_GEO_INCLUDE)) { if (!file_exists(JABALI_GEO_INCLUDE)) {
@@ -2847,7 +2859,7 @@ function findWafBaseConfig(): ?string
]; ];
foreach ($paths as $path) { foreach ($paths as $path) {
if (file_exists($path)) { if (file_exists($path) && isWafBaseConfigUsable($path)) {
return $path; return $path;
} }
} }
@@ -2855,6 +2867,28 @@ function findWafBaseConfig(): ?string
return null; return null;
} }
function isWafBaseConfigUsable(string $path): bool
{
if (!is_readable($path)) {
return false;
}
$content = file_get_contents($path);
if ($content === false) {
return false;
}
if (preg_match_all('/^\s*Include\s+("?)([^"\s]+)\1/m', $content, $matches)) {
foreach ($matches[2] as $includePath) {
if ($includePath === '/etc/modsecurity/modsecurity.conf' && !file_exists($includePath)) {
return false;
}
}
}
return true;
}
function wafApplySettings(array $params): array function wafApplySettings(array $params): array
{ {
$enabled = !empty($params['enabled']); $enabled = !empty($params['enabled']);
@@ -2870,6 +2904,7 @@ function wafApplySettings(array $params): array
if ($enabled) { if ($enabled) {
$baseConfig = findWafBaseConfig(); $baseConfig = findWafBaseConfig();
if (!$baseConfig) { if (!$baseConfig) {
file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n");
return ['success' => false, 'error' => 'ModSecurity base configuration not found']; return ['success' => false, 'error' => 'ModSecurity base configuration not found'];
} }

View File

@@ -1083,7 +1083,10 @@ configure_nginx() {
local jabali_includes="/etc/nginx/jabali/includes" local jabali_includes="/etc/nginx/jabali/includes"
mkdir -p "$jabali_includes" mkdir -p "$jabali_includes"
if [[ ! -f "$jabali_includes/waf.conf" ]]; then if [[ ! -f "$jabali_includes/waf.conf" ]]; then
echo "# Managed by Jabali" > "$jabali_includes/waf.conf" cat > "$jabali_includes/waf.conf" <<'EOF'
# Managed by Jabali
modsecurity off;
EOF
fi fi
if [[ ! -f "$jabali_includes/geo.conf" ]]; then if [[ ! -f "$jabali_includes/geo.conf" ]]; then
echo "# Managed by Jabali" > "$jabali_includes/geo.conf" echo "# Managed by Jabali" > "$jabali_includes/geo.conf"
@@ -2041,36 +2044,62 @@ configure_security() {
if [[ "$INSTALL_SECURITY" == "true" ]]; then if [[ "$INSTALL_SECURITY" == "true" ]]; then
info "Installing ModSecurity (optional WAF)..." info "Installing ModSecurity (optional WAF)..."
local module_pkg="" local module_pkg=""
if apt-cache show libnginx-mod-http-modsecurity2 &>/dev/null; then if apt-cache show libnginx-mod-http-modsecurity &>/dev/null; then
module_pkg="libnginx-mod-http-modsecurity2"
elif apt-cache show libnginx-mod-http-modsecurity &>/dev/null; then
module_pkg="libnginx-mod-http-modsecurity" module_pkg="libnginx-mod-http-modsecurity"
elif apt-cache show libnginx-mod-http-modsecurity2 &>/dev/null; then
module_pkg="libnginx-mod-http-modsecurity2"
elif apt-cache show nginx-extras &>/dev/null; then elif apt-cache show nginx-extras &>/dev/null; then
module_pkg="nginx-extras" module_pkg="nginx-extras"
else else
warn "ModSecurity nginx module not available in apt repositories" warn "ModSecurity nginx module not available in apt repositories"
fi fi
local modsec_lib=""
if apt-cache show libmodsecurity3t64 &>/dev/null; then
modsec_lib="libmodsecurity3t64"
elif apt-cache show libmodsecurity3 &>/dev/null; then
modsec_lib="libmodsecurity3"
fi
local crs_pkg="" local crs_pkg=""
if apt-cache show modsecurity-crs &>/dev/null; then if apt-cache show modsecurity-crs &>/dev/null; then
crs_pkg="modsecurity-crs" crs_pkg="modsecurity-crs"
fi fi
if [[ -n "$module_pkg" ]]; then if [[ -n "$module_pkg" ]]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$module_pkg" $crs_pkg 2>/dev/null || warn "ModSecurity install failed" DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$module_pkg" $modsec_lib $crs_pkg 2>/dev/null || warn "ModSecurity install failed"
# Ensure ModSecurity base config # Ensure ModSecurity base config
if [[ -f /etc/modsecurity/modsecurity.conf-recommended ]] && [[ ! -f /etc/modsecurity/modsecurity.conf ]]; then if [[ ! -f /etc/modsecurity/modsecurity.conf ]]; then
if [[ -f /etc/nginx/modsecurity.conf ]]; then
cp /etc/nginx/modsecurity.conf /etc/modsecurity/modsecurity.conf
elif [[ -f /etc/modsecurity/modsecurity.conf-recommended ]]; then
cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
elif [[ -f /usr/share/modsecurity-crs/modsecurity.conf-recommended ]]; then
cp /usr/share/modsecurity-crs/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
else
cat > /etc/modsecurity/modsecurity.conf <<'EOF'
SecRuleEngine DetectionOnly
SecRequestBodyAccess On
SecResponseBodyAccess Off
SecAuditEngine RelevantOnly
SecAuditLog /var/log/nginx/modsec_audit.log
EOF
fi
fi fi
# Create main include file for nginx if missing # Create main include file for nginx if missing
mkdir -p /etc/nginx/modsec mkdir -p /etc/nginx/modsec
if [[ ! -f /etc/nginx/modsec/main.conf ]]; then if [[ ! -f /etc/nginx/modsec/main.conf ]]; then
if [[ -f /usr/share/modsecurity-crs/crs-setup.conf ]]; then if [[ -f /usr/share/modsecurity-crs/owasp-crs.load ]]; then
cat > /etc/nginx/modsec/main.conf <<'EOF' cat > /etc/nginx/modsec/main.conf <<'EOF'
Include /etc/modsecurity/modsecurity.conf Include /etc/modsecurity/modsecurity.conf
Include /usr/share/modsecurity-crs/crs-setup.conf Include /usr/share/modsecurity-crs/owasp-crs.load
EOF
elif [[ -f /etc/modsecurity/crs/crs-setup.conf ]]; then
cat > /etc/nginx/modsec/main.conf <<'EOF'
Include /etc/modsecurity/modsecurity.conf
Include /etc/modsecurity/crs/crs-setup.conf
Include /usr/share/modsecurity-crs/rules/*.conf Include /usr/share/modsecurity-crs/rules/*.conf
EOF EOF
else else