Skip to content

[Feat] Server Security#1143

Merged
saeedvaziry merged 6 commits into
vitodeploy:4.xfrom
RichardAnderson:feat/server-hardening
Jun 4, 2026
Merged

[Feat] Server Security#1143
saeedvaziry merged 6 commits into
vitodeploy:4.xfrom
RichardAnderson:feat/server-hardening

Conversation

@RichardAnderson

@RichardAnderson RichardAnderson commented Jun 4, 2026

Copy link
Copy Markdown
Member

This pull request introduces a comprehensive server security management feature, adding new actions, controllers, jobs, and supporting enums to handle various security controls such as automatic updates, SSH password authentication, root login, and Fail2ban configuration. It also updates existing resources and command scheduling to support these features.

image

Summary by CodeRabbit

  • New Features
    • Security dashboard with per-control score, real-time updates, and actions for auto-updates, password auth, root login, Fail2ban and firewall.
    • Automatic updates: UI schedule builder, cron validation, and background runner to dispatch due updates.
    • Fail2ban install/configure/uninstall flows with interactive form and validation.
  • Tests
    • Comprehensive feature tests covering UI flows, scheduling, hardening actions, Fail2ban/firewall, and the auto-update runner.

@RichardAnderson RichardAnderson marked this pull request as draft June 4, 2026 17:36
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d3c22c7a-c2a8-42aa-b33d-aa0604a7c93d

📥 Commits

Reviewing files that changed from the base of the PR and between db5d4c5 and a66d1a2.

📒 Files selected for processing (1)
  • resources/js/pages/security/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • resources/js/pages/security/index.tsx

📝 Walkthrough

Walkthrough

Adds server security controls (SSH password/root login), auto-update scheduling, Fail2ban service implementation and orchestration, a Security dashboard UI, controller endpoints, queued workers to apply/detect changes, DB migration/API schema updates, and comprehensive feature tests.

Changes

Server Security Management

Layer / File(s) Summary
Security state contracts and persisted fields
app/Enums/SecurityControlStatus.php, app/Models/Server.php, app/Http/Resources/ServerResource.php, database/migrations/*, public/api-docs/openapi/schemas/Server.yaml, resources/js/types/*, app/Http/Controllers/ConsoleController.php
Adds SecurityControlStatus enum; Server gains auto_update_schedule, sshLoginUsers(), fail2ban(), security()/securityState()/securityScore() and fillable/migration; ServerResource/OpenAPI updated; ConsoleController token validation uses sshLoginUsers().
SSH security detection and hardening execution
app/SSH/OS/Security.php, resources/views/ssh/security/*.blade.php
Implements SSH security detection and apply logic via view-based SSH commands, with enable/disable/status templates that validate sshd/fail2ban and reload services.
Auto-update scheduling and execution
app/Actions/Server/Security/ManageAutoUpdate.php, app/Console/Commands/RunServerAutoUpdatesCommand.php, app/Console/Kernel.php, resources/views/ssh/os/upgrade.blade.php
ManageAutoUpdate validates and persists schedule; RunServerAutoUpdatesCommand iterates READY servers, validates cron syntax/timezone and triggers updates; Kernel schedules runner; upgrade template set to noninteractive.
Fail2ban service handler and lifecycle management
app/Services/Fail2ban/Fail2ban.php, app/Actions/Server/Security/ConfigureFail2ban.php, app/Jobs/Server/Security/ConfigureFail2banJob.php, app/Providers/ServiceTypeServiceProvider.php, resources/views/ssh/services/fail2ban/*.blade.php
Registers Fail2ban service type; adds Fail2ban service implementation (validation, install/configure/uninstall/version/logs); SSH templates for install/configure/jail/uninstall; ConfigureFail2ban action and job to apply and broadcast service updates.
SSH control actions and jobs
app/Actions/Server/Security/ManagePasswordAuth.php, app/Actions/Server/Security/ManageRootLogin.php, app/Actions/Server/Security/CheckSecurityState.php, app/Jobs/Server/Security/ApplyPasswordAuthJob.php, app/Jobs/Server/Security/ApplyRootLoginJob.php, app/Jobs/Server/Security/DetectSecurityJob.php
Actions validate input, set UPDATING, guard against disabling root when Vito connects as root, and dispatch queued jobs; jobs apply changes, persist READY/FAILED detected state, log failures, and broadcast security.updated.
Security endpoints for page and mutations
app/Http/Controllers/SecurityController.php
New SecurityController renders the Security page payload and exposes endpoints wiring to action classes for auto-update, password/root toggles, check, firewall/fail2ban install/configure/uninstall.
Security page UI, forms, schedule builder, and navigation updates
resources/js/pages/security/*, resources/js/types/security.d.ts, resources/js/layouts/server/layout.tsx, resources/js/components/*
Adds Security dashboard, ScheduleBuilder, Fail2banForm, TypeScript types, dialog registration, and sidebar navigation updates (top-level Security with ShieldIcon); UI wired to controller routes and socket events.
End-to-end feature validation
tests/Feature/SecurityTest.php
Comprehensive tests for page, auto-update enable/disable/validation, password-auth/root-login flows and constraints, scoring logic, Fail2ban/firewall lifecycle and input hardening, and auto-update runner dispatch behavior.

Sequence Diagram

sequenceDiagram
  participant Controller as SecurityController
  participant Action as ManageRootLogin
  participant Job as ApplyRootLoginJob
  participant Security as SSH/OS/Security
  participant Server as Server Model
  participant Socket as SocketEvent

  Controller->>Action: update(server, {enabled: false})
  Action->>Action: validate & guard
  Action->>Server: refresh & jsonUpdate(UPDATING)
  Action->>Job: dispatch(server,false) on ssh queue
  Job->>Security: setRootLogin(false) (run templates over SSH)
  Security-->>Job: effective setting result
  Job->>Server: writeState(READY, detected)
  Job->>Socket: broadcast security.updated {server_id}
Loading

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Possibly related PRs

  • Suggested reviewers

    • saeedvaziry

🐰 A security sprint hops through the undergrowth,
SSH hardened, Fail2ban caught, schedules set in stone,
Auto-updates dance with cron's gentle beat,
While dashboard cards sing of controls now complete. 🛡️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[Feat] Server Security' is directly related to the changeset. The PR introduces comprehensive server security management features including automatic updates, SSH authentication controls, Fail2ban configuration, and security state detection.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Actions/Server/Security/CheckSecurityState.php`:
- Line 12: DetectSecurityJob is being run synchronously via
DetectSecurityJob::dispatchSync($server), which can block the request; change it
to queued dispatch so the job runs asynchronously (e.g., use
DetectSecurityJob::dispatch($server) or dispatch(new DetectSecurityJob($server))
and ensure it goes to the queue), remove blocking call and let the UI update via
socket events instead. Ensure any required job/queue configuration (implements
ShouldQueue, queue connection) is present on DetectSecurityJob so it is actually
queued.

In `@app/Actions/Server/Security/ConfigureFail2ban.php`:
- Around line 26-35: The update to $service->type_data and the subsequent
dispatch of ConfigureFail2banJob must be atomic: wrap the $service->type_data
assignment, $service->save(), and dispatch(new
ConfigureFail2banJob($service))->onQueue('ssh') inside a database transaction
(use DB::transaction or DB::beginTransaction/commit/rollBack) so that if
dispatching the job fails the save is rolled back; locate these calls in
ConfigureFail2ban.php and perform the save and dispatch within the same
try/catch transaction block, rethrow or handle errors after rolling back.

In `@app/Actions/Server/Security/ManageRootLogin.php`:
- Around line 24-28: The current check in ManageRootLogin.php always throws a
ValidationException when $server->getSshUser() === 'root', blocking even
enable=true updates; change the guard to only reject when the request is trying
to disable root login (i.e. when the incoming "enabled" value is false/boolean
falsey). Update the conditional around ValidationException::withMessages to
combine $server->getSshUser() === 'root' AND the parsed incoming enabled value
(use the request or method param that carries "enabled"), so the error is thrown
only on disable attempts and leaves enable=true requests allowed.

In `@app/Jobs/Server/Security/ApplyRootLoginJob.php`:
- Around line 25-31: The current guard in ApplyRootLoginJob.php checks
$this->server->getSshUser() === 'root' and blocks both enable and disable
attempts; change it so it only blocks disable requests: detect the requested
action (e.g. the job's payload flag such as $this->request->enable or
$this->enable / $this->data['enable']) and only run the
writeState(ServerControlStatus::FAILED),
ServerLog::log('disable-root-login-failed', ...) and return when the SSH user is
'root' AND the request is to disable root login; keep enable flows unchanged so
enabling root login succeeds.

In `@app/Services/Fail2ban/Fail2ban.php`:
- Around line 128-135: The version() method in Fail2ban calls
$this->service->server->ssh()->exec(...) which may throw SSHError; update the
PHPDoc for the version() method to include a `@throws` SSHError annotation (or
fully-qualified \SSHError) so the docblock reflects the possible exception.
Ensure the SSHError symbol used in the docblock matches the actual exception
class name/namespace used by the ssh() implementation (adjust the docblock
import/namespace if needed).

In `@app/SSH/OS/Security.php`:
- Line 23: The current return expression in the Security class that does:
str($result)->after('VITO_PASSWORD_AUTH:')->trim()->startsWith('yes') treats
missing/unparsable marker as false; change it to fail-closed by first detecting
whether the marker and a non-empty value exist and return true if the marker is
missing or the extracted value is empty, otherwise return the boolean of
startsWith('yes'). Update the method in Security.php to explicitly check for
presence/emptiness of the after('VITO_PASSWORD_AUTH:')->trim() result and only
call startsWith('yes') when a value exists; if not present, return true
(password auth enabled).

In `@resources/js/pages/security/components/schedule-builder.tsx`:
- Around line 17-31: parseSchedule and buildSchedule currently allow
out-of-range hours/minutes and invalid day-of-week tokens to propagate; update
both to normalize and validate values: in parseSchedule parse m and h as
integers, clamp h to 0–23 and m to 0–59, format time with padStart after
clamping, and validate dow so only '*' or a numeric string between 1 and 7 is
accepted (fall back to '1' if invalid); in buildSchedule keep the existing
clamping for hh/mm but also validate the incoming weekday (use '*' for daily,
and if weekly ensure weekday is numeric 1–7 else default to '1') so the returned
cron is always well-formed (reference functions parseSchedule and
buildSchedule).

In `@resources/js/pages/security/index.tsx`:
- Around line 82-85: Replace the bare onClick submit with proper form submit
semantics: wrap the editable fields in a <form> that has an onSubmit handler
which calls e.preventDefault() and then invokes the existing save function
(referencing the save function and the useForm instance named form), give the
form a unique id, and change the Button to a submit control by removing onClick
and setting type="submit" form="<that-id>" (keeping disabled={form.processing}
and the LoaderCircleIcon rendering). Ensure the new onSubmit uses the form id
and prevents default before calling save so the existing save logic and
form.processing state are preserved.
- Around line 334-336: The handler currently triggers router.reload for any
event matching event.type?.startsWith('service.'), causing reloads from other
servers; update the condition to also verify the event is for the current server
(e.g. compare event.serverId or event.data.serverId to the local server id
variable such as currentServer?.id or serverId) before calling router.reload({
only: ['fail2ban', 'firewall', 'score'] }); keep the existing service.* type
check and use safe optional chaining when reading event.serverId so only events
for the active server trigger the reload.

In `@resources/js/types/server.d.ts`:
- Line 26: The Server type's auto_update_schedule property is currently declared
as a string but the API can return null; update the Server type declaration so
auto_update_schedule allows null (e.g., change its type to include null) to
match the backend resource and avoid runtime null-handling bugs—locate the
Server interface/type and modify the auto_update_schedule field accordingly.

In `@resources/views/ssh/security/disable-password-auth.blade.php`:
- Around line 18-22: The check only inspects sshd's effective
"passwordauthentication" value (EFFECTIVE) but must also verify
"kbdinteractiveauthentication" to ensure keyboard-interactive auth is disabled;
update the logic around the sshd -T parsing (the command assigning EFFECTIVE) to
also capture the effective kbdinteractiveauthentication value (e.g.,
KBD_EFFECTIVE) by extracting the respective keys from sshd -T output, then
require both EFFECTIVE and KBD_EFFECTIVE equal "no" before declaring success; if
either is not "no", emit a single error message that includes both effective
values (referencing the variable names EFFECTIVE and KBD_EFFECTIVE and the sshd
-T extraction) and exit 1.

In `@resources/views/ssh/security/enable-password-auth.blade.php`:
- Around line 8-13: The current check uses "sshd -t" which only validates syntax
but not effective settings; after verifying syntax (sshd -t) and before calling
"sudo systemctl reload ssh", run "sshd -T" and inspect the effective directive
(e.g., grep -i 'passwordauthentication') to ensure PasswordAuthentication is set
to "yes" (and other related directives like PermitEmptyPasswords if needed); if
the effective config does not reflect the intended setting, log an error and
exit nonzero instead of reloading, otherwise proceed to "sudo systemctl reload
ssh" and return success.

In `@resources/views/ssh/security/enable-root-login.blade.php`:
- Around line 8-13: After the syntax check with "sudo sshd -t" also verify the
effective PermitRootLogin value using "sudo sshd -T" (which expands included
files and precedence) and assert the returned "permitrootlogin" equals the
expected policy; if it does not match, print a clear VITO_SSH_ERROR including
the effective value and exit non‑zero instead of reloading. Place this check
between the sshd -t block and the "sudo systemctl reload ssh" call, referencing
"sshd -t", "sshd -T" and the "PermitRootLogin"/"permitrootlogin" setting.

In `@tests/Feature/SecurityTest.php`:
- Around line 295-312: The test calls travelTo(...) to freeze time but never
resets it, causing clock leakage; wrap the time-dependent section (the
travelTo(...) call, the server setup, $this->artisan('servers:auto-update') call
and assertions) in a try/finally and call travelBack() in the finally block (or
at minimum call travelBack() after the assertions) so global test time is
restored; locate the travelTo(...) usage in SecurityTest
(tests/Feature/SecurityTest.php) and add travelBack() to restore time around the
artisan call and Queue/Assert checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f6c7c77a-4ab6-47f6-aaed-399814c184d4

📥 Commits

Reviewing files that changed from the base of the PR and between a47047c and 1e1a5b7.

📒 Files selected for processing (41)
  • app/Actions/Server/Security/CheckSecurityState.php
  • app/Actions/Server/Security/ConfigureFail2ban.php
  • app/Actions/Server/Security/ManageAutoUpdate.php
  • app/Actions/Server/Security/ManagePasswordAuth.php
  • app/Actions/Server/Security/ManageRootLogin.php
  • app/Console/Commands/RunServerAutoUpdatesCommand.php
  • app/Console/Kernel.php
  • app/Enums/SecurityControlStatus.php
  • app/Http/Controllers/ConsoleController.php
  • app/Http/Controllers/SecurityController.php
  • app/Http/Resources/ServerResource.php
  • app/Jobs/Server/Security/ApplyPasswordAuthJob.php
  • app/Jobs/Server/Security/ApplyRootLoginJob.php
  • app/Jobs/Server/Security/ConfigureFail2banJob.php
  • app/Jobs/Server/Security/DetectSecurityJob.php
  • app/Models/Server.php
  • app/Providers/ServiceTypeServiceProvider.php
  • app/SSH/OS/Security.php
  • app/Services/Fail2ban/Fail2ban.php
  • database/migrations/2026_06_03_234007_add_auto_update_schedule_to_servers_table.php
  • public/api-docs/openapi/schemas/Server.yaml
  • resources/js/components/app-sidebar.tsx
  • resources/js/components/dialogs/registry.ts
  • resources/js/layouts/server/layout.tsx
  • resources/js/pages/security/components/fail2ban-form.tsx
  • resources/js/pages/security/components/schedule-builder.tsx
  • resources/js/pages/security/index.tsx
  • resources/js/types/security.d.ts
  • resources/js/types/server.d.ts
  • resources/views/ssh/os/upgrade.blade.php
  • resources/views/ssh/security/disable-password-auth.blade.php
  • resources/views/ssh/security/disable-root-login.blade.php
  • resources/views/ssh/security/enable-password-auth.blade.php
  • resources/views/ssh/security/enable-root-login.blade.php
  • resources/views/ssh/security/password-auth-status.blade.php
  • resources/views/ssh/security/root-login-status.blade.php
  • resources/views/ssh/services/fail2ban/configure.blade.php
  • resources/views/ssh/services/fail2ban/install.blade.php
  • resources/views/ssh/services/fail2ban/jail.blade.php
  • resources/views/ssh/services/fail2ban/uninstall.blade.php
  • tests/Feature/SecurityTest.php

Comment thread app/Actions/Server/Security/CheckSecurityState.php Outdated
Comment thread app/Actions/Server/Security/ConfigureFail2ban.php
Comment thread app/Actions/Server/Security/ManageRootLogin.php Outdated
Comment thread app/Jobs/Server/Security/ApplyRootLoginJob.php Outdated
Comment thread app/Services/Fail2ban/Fail2ban.php
Comment thread resources/js/types/server.d.ts Outdated
Comment thread resources/views/ssh/security/disable-password-auth.blade.php
Comment thread resources/views/ssh/security/enable-password-auth.blade.php
Comment thread resources/views/ssh/security/enable-root-login.blade.php
Comment thread tests/Feature/SecurityTest.php

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot stopped reviewing on behalf of RichardAnderson due to an error June 4, 2026 18:37
@RichardAnderson RichardAnderson marked this pull request as ready for review June 4, 2026 18:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@resources/js/pages/security/index.tsx`:
- Around line 368-369: The Tailwind class on the warning icon is using the
invalid suffix-important form; update the JSX for TriangleAlertIcon (inside the
Alert component) to use a valid Tailwind class such as "text-warning" or, if you
need the !important variant, "!text-warning" instead of "text-warning!" so the
utility will apply correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 25e441ce-9dcf-4366-ad7f-7156650a7489

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1a5b7 and db5d4c5.

📒 Files selected for processing (10)
  • app/Actions/Server/Security/CheckSecurityState.php
  • app/Actions/Server/Security/ManageRootLogin.php
  • app/Jobs/Server/Security/ApplyRootLoginJob.php
  • app/Jobs/Server/Security/DetectSecurityJob.php
  • app/SSH/OS/Security.php
  • resources/js/pages/security/index.tsx
  • resources/js/types/server.d.ts
  • resources/views/ssh/security/disable-password-auth.blade.php
  • resources/views/ssh/security/enable-password-auth.blade.php
  • resources/views/ssh/security/enable-root-login.blade.php
🚧 Files skipped from review as they are similar to previous changes (6)
  • app/Actions/Server/Security/CheckSecurityState.php
  • resources/views/ssh/security/enable-password-auth.blade.php
  • app/Jobs/Server/Security/DetectSecurityJob.php
  • app/Actions/Server/Security/ManageRootLogin.php
  • app/SSH/OS/Security.php
  • app/Jobs/Server/Security/ApplyRootLoginJob.php

Comment thread resources/js/pages/security/index.tsx Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@saeedvaziry saeedvaziry merged commit 801f0e5 into vitodeploy:4.x Jun 4, 2026
4 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants