Commit Graph

4 Commits (4f4b7225f552aaec817cc64fcd78dd16ca0735c4)

Author SHA1 Message Date
boban e792e9a3af feat(fail2ban): jail status, banned-IP list + unban, manual ban, whitelist
Addresses "ich sehe nicht welche IPs gebannt sind / entsperren / whitelist". The
server-detail page gains a "fail2ban-Status" panel.

New Fail2banService (owns all fail2ban I/O):
- status(): jails with currently/total failed+banned + the banned IP list, plus the
  whitelist; sentinel-guarded, and a CLIENT_OK marker so an active service whose
  fail2ban-client query fails is reported as a read error, not "0 jails".
- unban() (idempotent: "not banned" = success), manual ban() GUARDED against loopback
  (whole 127.0.0.0/8 + ::1) and Clusev's own SSH source (canonical inet_pton compare).
- readConfig()/writeTuning()/writeIgnoreip(): tuning and whitelist live in SEPARATE
  zz- drop-ins and each writes only its own keys, so a whitelist change can never
  rewrite the ban policy (and vice-versa). The legacy single-file zz-clusev.local is
  removed on tuning save (migration). ignoreip is preserved VERBATIM (hostnames/CIDRs,
  continuation lines) — loopback always re-seeded; add/remove report no-ops so the
  audit only records real changes.

UI: jails + banned IPs each with "Entsperren" (direct, audited), whitelist editor
(add/remove, audited), "IP sperren" via an R5 modal (modals.fail2ban-ban). Jail/IP
args are Js::from()-encoded (remote-sourced, injection-safe). Direct handlers catch
SSH failures. Tuning modal migrated to Fail2banService.writeTuning.

MaintenanceService slimmed to package updates only (fail2ban moved out).

Pint clean; Codex review clean (IPv6 guard, verbatim ignoreip, decoupled drop-ins,
legacy migration, read-error detection all hardened); R12 — server-detail HTTP 200,
0 console errors, fail2ban panel + jail + whitelist render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:27:55 +02:00
boban 4bfa367ccf feat(os): OS abstraction — manage updates/hardening/firewall across apt, dnf & zypper
Foundation so Clusev no longer assumes Debian/apt/ufw/systemd. A host's OS is
resolved once (cached) and every package/firewall/service decision asks it.

New app/Support/Os/:
- OsProfile  — family + package manager + firewall tool + service manager, plus
  supports(feature) returning NULL or a German reason (graceful degradation).
- OsDetector — one unprivileged probe (/etc/os-release + `command -v` with sbin on
  PATH + which firewall is RUNNING); family from ID/ID_LIKE cross-checked against
  installed binaries; cached 1h (60s when nothing detected). Readability-guards the
  `.` source so a host without /etc/os-release still falls back by package manager.
- PackageManager — apt/dnf/zypper command strings (pending count, apply, install,
  is-installed); zypper exit 102/103 normalized to success.
- FirewallTool   — ufw + firewalld enable/disable. firewalld opens ssh/80/443 in the
  PERMANENT config BEFORE the daemon starts filtering (handles running + stopped),
  preserving the no-lockout guard for custom SSH ports.

Integration:
- FirewallService enable/disable now OS-aware (ufw or firewalld) with a support gate.
- MaintenanceService: hasApt -> updateSupport(); pendingUpdates() + applyUpgrades()
  across managers (dnf check-update exit 100 handled).
- HardeningService: state()/commandFor() per-OS (dpkg vs rpm, ufw vs firewalld, apt
  periodic vs dnf-automatic; yum-only hosts gate auto-updates). Each row carries
  supported/reason; unsupported features render muted with a German note instead of
  a toggle. apply() refuses unsupported features gracefully.
- Server-Details surfaces the detected System / package manager / firewall.
- SystemUpdate modal + view: OS-neutral copy; audit action system.package_upgrade.

Arch (pacman) & Alpine (apk/OpenRC) are detected and gracefully disabled where
unsupported. Debian/ufw/systemd path verified behaviourally identical on the live
fleet. Pint clean; Codex review clean (fixed firewalld lockout + os-release guard +
yum/dual-firewall/zypper edge cases); R12 — 9 routes 200, 0 console errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:30:25 +02:00
boban 0f57074e6f chore(audit): full code sweep — fix open-redirect, drop dead code, clean docs
Full-codebase audit + automatic cleanup (no behavioural change for users).

Security
- ServerSwitcher: post-switch redirect is now reduced to a SAME-ORIGIN
  relative path. Rejects off-host referers, non-http(s) schemes
  (javascript:/data:), different ports (https://host:444/x -> /x), and
  protocol-relative tricks (//host, /\host). Closes a Referer open-redirect
  (Codex P2).
- EditCredential: a key passphrase is stored only for key auth, never for
  password auth.

Removed dead code
- FirewallService::status/allow/deny (+ orphaned clampPort)
- Server::auditEvents(), SshCredential::scopeActive(), unused 'bell' icon

Cleanups / improvements
- Files list + editor use a dedicated 'file' icon (was the audit icon)
- Import RuntimeException/Throwable instead of FQN; fix stale docblocks
  (FirewallService, HardeningService) and the Files delete comment
- Quote base64 in the sudo command path (defensive, consistent)
- Null-safe Auth::user()?->email in Settings; single domain() read in hasTls()

Release
- Bump 0.1.0 -> 0.1.1 + CHANGELOG entry

Verified: Pint clean, Codex review clean (no actionable regressions),
R12 browser check — 9 routes HTTP 200, 0 console errors, lazy pages loaded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:54:26 +02:00
boban 05f8ce49b4 feat: add-server, apt updates, fail2ban config; clean modals; drop storage internals
Operator feedback: no way to add servers; irrelevant storage-internals text; fail2ban
only on/off; raw shell commands shown; no apt update/upgrade.

- Add server: CreateServer modal (name, IP/host, SSH port, user, password|key, optional
  label) on the Servers index → creates server + encrypted credential ATOMICALLY (DB
  transaction). Strict IP/hostname validation (filter_var + hostname; rejects 999.x, ':').
- System updates (Debian/Ubuntu only): MaintenanceService + SystemUpdate modal — shows the
  pending-update count (or "unbekannt" when undeterminable) and runs apt update && upgrade
  as root; gated to apt hosts.
- fail2ban configuration: Fail2banConfig modal — Sperrdauer / Max. Fehlversuche / Zeitfenster,
  written to a Clusev-owned jail.d drop-in (zz-clusev.local, last-wins; never touches the
  operator's jail.local/jails). Durations kept verbatim in fail2ban's native grammar
  (600, 10m, 1h 30m, -1). Reads the EFFECTIVE [DEFAULT] across files; refuses to save when the
  current policy couldn't be read (no overwrite with unseen defaults); reloads fail2ban only
  when already active (never starts it).
- Modals: removed the raw "Befehle (als root)" preview + raw stdout dumps from the hardening
  modal — clean German description + result only.
- System page: dropped the .env/Datenbank storage-internals callout (irrelevant to the user).

R15 — Codex gate: `codex review --uncommitted` run iteratively; fixed every finding across 9
rounds (fail2ban jail clobbering, section/precedence, composite/-1 durations, reload-starting-
inactive, read-failure propagation, glob exit code, apt-count failure, atomic server create,
IP validation) until clean — 0 security issues throughout. Live-verified on 10.10.90.162;
R12: Servers/Detail/System 200 / 0 console errors, modals open, hardening modal shows no commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:19:55 +02:00