This repository was archived by the owner on Jan 3, 2023. It is now read-only.
Change config file name - #160
Open
sandmanstorm wants to merge 258 commits into
Open
Conversation
creating .htpasswd and generating secure password for client-config
some installation visual touchups
README: - Rewrite title and summary to reflect current feature set - Add feature table: dashboard, cert management, SMTP, roles, notifications - Update supported platforms table (Ubuntu 22/24, Debian 11/12, RPi OS 32/64-bit) - Add Admin Panel section documenting every page and its functionality - Document certificate management workflow and sudoers requirement - Document role-based access (super-admin vs read-only) - Document email notification setup and requirements - Update update.sh section to describe backup, migration, and auto-patching - Update uninstall.sh section to describe confirmation prompt and scope - Add How It Works architecture diagram - Update libraries table: Bootstrap 5, Bootstrap Icons, bootstrap-table 1.22, jQuery 3.7; remove Bootstrap Datepicker and X-editable (no longer used) - Remove Bower/npm references throughout - Add Reporting Issues section with required info checklist CHANGELOG: - Add v1.0.0 entry covering all changes made in this release Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ctions
functions.php line 88: str_contains($line, "\t") requires PHP 8.0+;
replaced with strpos($line, "\t") !== false (PHP 5+)
functions.php line 130: match($status){...} requires PHP 8.0+;
replaced with array lookup using isset() ternary (PHP 7.0+)
mailer.php lines 23-24: fn() => arrow function syntax requires PHP 7.4+;
replaced with regular closures using use(&$conn) (PHP 5.3+)
All PHP files now require PHP 7.2+ at minimum, covering:
Debian 10 (PHP 7.3), Debian 11 (PHP 7.4), Debian 12 (PHP 8.2),
Ubuntu 20.04 (PHP 7.4), Ubuntu 22.04 (PHP 8.1), Ubuntu 24.04 (PHP 8.3),
Raspberry Pi OS Bullseye (PHP 7.4), Bookworm (PHP 8.2)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The sed -i on /etc/needrestart/needrestart.conf was running without sudo. online-install.sh runs as a regular user so it cannot write to /etc/, causing: "sed: couldn't open temporary file /etc/needrestart/sedcXXXXX: Permission denied" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous approach (sed -i on /etc/needrestart/needrestart.conf) failed with "Permission denied" even with sudo because sed -i creates a temp file in the target directory, which can fail in certain pipe/sudo contexts. New approach: export NEEDRESTART_MODE=a before any apt-get calls. needrestart respects this environment variable and runs in automatic mode (no interactive prompts) without requiring any file to be written. - online-install.sh: remove needrestart config file edit; export NEEDRESTART_MODE=a and DEBIAN_FRONTEND=noninteractive at the top; use sudo -E for all apt-get and install.sh calls so the environment is preserved through sudo - install.sh: add export NEEDRESTART_MODE=a alongside DEBIAN_FRONTEND so it is suppressed when install.sh is run directly as well Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a machine has multiple PHP versions installed (e.g. php7.4 from a prior attempt and php8.4 from the current install), Apache may load an old PHP module that doesn't match the CLI PHP version, causing parse errors for version-specific syntax. install.sh: before enabling the target PHP module, iterate over all currently loaded PHP modules (via apache2ctl -M) and disable any that don't match the CLI PHP version. This prevents conflicts when upgrading from a previous installation. update.sh: after updating files, detect the Apache-loaded PHP module version and compare it to the CLI PHP version. If they differ, disable the old module and enable the correct one before restarting Apache. This automatically self-heals the mismatch on every update run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sidebar: add 'by Armin' GitHub link below the signed-in user block, styled as a subtle credit with hover highlight. Login page: add small 'by Armin - github.com/arvage/OpenVPN-Admin' link below the login card. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… runner ADD COLUMN IF NOT EXISTS is MariaDB-only syntax. MySQL raises error 1064 (syntax error) when it encounters it, breaking the migration on any MySQL-backed installation. sql/schema-10.sql: remove IF NOT EXISTS from all four ALTER TABLE ADD COLUMN statements. CREATE TABLE IF NOT EXISTS is kept (valid MySQL syntax). include/functions.php: add execMigrationSql() helper that splits a SQL file on semicolons and executes each statement individually, silently skipping MySQL error 1060 (Duplicate column name) and 1050 (Table already exists). This makes migrations idempotent and safe to re-run on both MySQL and MariaDB without IF NOT EXISTS support. migration.php: switch from bdd->exec($sql) to execMigrationSql() so the per-statement error handling applies to all future migrations. index.php: same change in the first-time web installer flow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous check (SHOW TABLES LIKE 'admin') returned true as soon as the admin table was created by the SQL migration, even when no admin users existed yet. This caused the installation page to redirect to login immediately after a fresh migration run, with no way to create the first admin account. Now queries COUNT(*) FROM admin instead — returns false if the table is missing (exception caught) or empty, true only when at least one admin user exists. This correctly represents the real "installed" state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove redundant redirect-gateway directive from all client templates; the server already pushes redirect-gateway def1 via server.conf. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace deprecated comp-lzo with compress lzo on server and all client templates. The old directive uses a different framing format in newer OpenVPN builds, causing "Bad LZO decompression header byte" mismatches. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The server was being migrated to compress lz4-v2 by install.sh/update.sh but client configs kept comp-lzo, causing "Bad LZO decompression header byte" errors. Change all templates to compress lz4-v2 and add client config patching to both install.sh and update.sh so server and clients always stay in sync. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e email editing - Dashboard already reads OpenVPN status file and auto-refreshes every 10s; user table now also refreshes every 10s on the Users page - Notification bell in topbar (super-admin only): shows unread badge and dropdown with last 50 events; notifications fire on user add, edit, and delete; clicking bell or "Mark all read" clears the unread state per-admin - Profile button in topbar opens a modal for any admin to update their own email (stored in admin_mail); email pre-fills from the database on modal open - Schema-11 migration adds the notification table; new installations pick it up automatically; existing installs run: php migration.php /path/to/www Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Path traversal (RCE): - update_config now validates config_file against an explicit allowlist of the 4 permitted paths; arbitrary file writes are no longer possible Password hash exposure: - user_pass and admin_pass are no longer included in ?select=user / ?select=admin API responses; hashes never leave the server Authorization bypass (cert download): - cert_download handler now calls requireSuperAdmin(); read-only admins can no longer download private key material CSRF: - generateCsrfToken() / verifyCsrfToken() added to functions.php - CSRF token generated per-session and exposed as window.CSRF_TOKEN in index.php - jQuery $.ajaxPrefilter automatically attaches the token to every POST request - grids.php validates the token at the top of all POST handling Stored XSS (certificate CN): - listCertificates() now htmlspecialchars() the CN before returning JSON - loadCertificates() JS rebuilt with safe DOM APIs ($().text(), $().attr()) instead of string concatenation Stored XSS (user email / phone / admin fields): - userActionsFormatter, adminActionsFormatter, passFormatter, adminPassFormatter all rebuilt with jQuery DOM construction and .attr() to prevent attribute injection; no user data is concatenated into raw HTML strings Privilege escalation (fail-open role): - getCurrentAdminRole() now returns 'read-only' (not 'super-admin') when the role field is empty or when a DB exception occurs; system fails closed Session cookies: - session_set_cookie_params() with httponly=true, samesite=Strict, secure=auto added before session_start() in both index.php and grids.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New sidebar page (Management > Fail2Ban) for super-admins:
- Reads all configured jails and their banned IP lists via fail2ban-client
- Summary stats: currently banned count, active jail count, currently failing
- Per-jail cards showing banned IPs with red header when bans are active
- Unban button per IP (confirm dialog, instant reload)
- Manual Ban IP modal with IP input + jail selector
- Auto-refreshes every 10 seconds when the page is active
- Manual refresh button
Backend (include/grids.php):
- GET ?select=fail2ban → getFail2BanStatus() (super-admin only)
- POST ban_ip → validates IP (FILTER_VALIDATE_IP) and jail name
(/^[a-zA-Z0-9_-]+$/) then calls fail2ban-client
- POST unban_ip → same validation, calls fail2ban-client unbanip
Security:
- IP addresses validated with filter_var(FILTER_VALIDATE_IP)
- Jail names validated with strict regex before shell use
- escapeshellarg() wraps all shell parameters
- Both ban/unban require super-admin role
Setup: web server needs passwordless sudo for fail2ban-client:
echo "www-data ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client" \
| sudo tee /etc/sudoers.d/openvpn-admin-fail2ban
sudo chmod 440 /etc/sudoers.d/openvpn-admin-fail2ban
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fail2Ban: - Added fail2ban package to apt-get install for Ubuntu, Debian, and Raspbian - Extended /etc/sudoers.d/openvpn-admin to grant the web server user passwordless sudo access to /usr/bin/fail2ban-client (required by the Fail2Ban admin page) - Writes /etc/fail2ban/filter.d/openvpn.conf: matches TLS auth errors, AUTH_FAILED, and TLS key negotiation failures in the OpenVPN server log - Writes /etc/fail2ban/jail.d/openvpn-admin.conf: enables sshd and openvpn jails with 5-attempt / 1-hour-ban defaults; port and protocol are set from the installer variables so they match the configured VPN setup - Enables and restarts fail2ban service - Summary output now mentions fail2ban is active and points to the admin panel Interactive prompts: - Removed `read -t 120` timeout from the public-IP and VPN-name prompts; the 120-second wait persisted even after the user pressed Enter because `read -t` continues running until its timer expires regardless of input. Both prompts now use plain `read -p` which returns immediately when Enter is pressed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sudoers block always writes both entries (EasyRSA + fail2ban-client) unconditionally, replacing the old "only if missing" guard — ensures existing installations pick up the fail2ban-client permission on update - Installs fail2ban via apt-get if fail2ban-client is not already in PATH - Reads port and protocol from the existing /etc/openvpn/server.conf so the OpenVPN jail matches whatever the server is actually running on - Writes /etc/fail2ban/filter.d/openvpn.conf and /etc/fail2ban/jail.d/openvpn-admin.conf (idempotent: safe to overwrite on every update run) - Enables and restarts fail2ban; skips gracefully if not available Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README: - Add Fail2Ban, In-app Notifications, and Admin Profile to features table - New Fail2Ban section under Admin Panel (live status, unban, manual ban, sudo setup note) - New Profile section under Admin Panel - Updated Users section to mention in-app notifications - Updated installer section to list what is set up automatically (fail2ban, sudoers for fail2ban-client) - Updated update.sh description to include fail2ban and sudoers changes - New Security section with a hardening table covering all 7 fixes - Updated How It Works diagram to include fail2ban and notification table - Added fail2ban version to Reporting Issues checklist CHANGELOG: - New 1.1.0 entry covering: fail2ban integration, in-app notifications, admin profile editing, dashboard auto-refresh, 7 security fixes, installer prompt bug fix, and update.sh sudoers idempotency fix ISSUE_TEMPLATE: - Added fail2ban version field - Added OpenVPN version field - Added admin panel version field - Added structured steps-to-reproduce section - Added fail2ban log snippet field Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…date Adds a git pull step at the start of update.sh so that running sudo ./update.sh /var/www is now the single command needed to go from any state to fully up-to-date (previously required a manual git pull first, which led to the notification table migration being skipped). The pull runs as the repo directory owner rather than root to avoid changing file ownership of the repo. Falls back gracefully with a warning if git is unavailable or the pull fails, so the rest of the update (file copy, migrations, fail2ban config, Apache reload) still runs against whatever local files are present. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JS bugs fixed: - refreshNotifications() was never called after add/edit/delete user — the badge would only update on the 30-second poll, making it appear as if notifications weren't working. Now called immediately in all three success callbacks (add, edit via $.when.done, delete in confirm modal). - Bell click now calls refreshNotifications() unconditionally before mark-read, so the dropdown is always fresh when opened regardless of whether the 30-second interval has fired. PHP bug fixed: - ?select=notifications catch block was returning is_super:false when the notification table didn't exist, which caused JS to bail silently and leave "Loading…" stuck in the dropdown forever. Now returns is_super:true with setup_needed:true so JS shows an actionable message instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ifications Settings → Notifications now has a second section (Admin Notifications) with three toggle switches below the existing email notification toggles: - User Added (default on) - User Edited (default off — too frequent/noisy) - User Deleted (default on) These control whether the in-app bell notification is created when a super-admin performs the corresponding user action. Implementation: - schema-12.sql: adds notify_admin_user_add, notify_admin_user_edit, notify_admin_user_delete columns (TINYINT DEFAULT 1/0/1) to smtp_settings - createNotification() now queries smtp_settings before inserting; if the toggle for that event type is off, the notification is silently skipped - save_smtp handler persists the three new fields alongside existing ones - loadSmtpSettings() in JS reads and applies the new values on page load, defaulting to add=on / edit=off / delete=on when the column is not yet present (pre-migration) - notif-form submit in JS sends the three new fields to save_smtp Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each row in the Web Admins table now has a pencil (edit) button that opens a small modal pre-populated with the admin's current email address. Saving POSTs set_admin with name=admin_mail, which the existing backend already accepts — no PHP changes needed. The role-toggle and delete buttons are unchanged and still hidden for the current admin's own row. The edit email button is shown for all rows (including self) so a super-admin can also correct their own email from the grid in addition to the Profile button in the topbar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README: - Admins section now describes the three per-row action buttons (edit email, toggle role, delete) and notes that email can be edited for any admin from the grid - Profile section updated to mention super-admins can also edit other admins' emails from the Admins grid - Settings → Notifications split into two groups: email notifications and admin in-app notification toggles with defaults documented - Update section simplified: update.sh now auto-pulls from git so the separate git pull step is no longer needed CHANGELOG: - New 1.1.1 entry covering: admin email edit in grid, per-type notification toggles, notification bell fixes (immediate refresh, setup_needed message, fresh on click), and update.sh auto-pull Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IP Banned and IP Unbanned events now create in-app notifications for
super-admins, consistent with user add/edit/delete notifications
- Two new toggles in Settings → Notifications (Admin Notifications section):
IP Banned — default on
IP Unbanned — default on
Backed by notify_admin_ban and notify_admin_unban columns (schema-13)
- Bell updates immediately after a ban or unban action (refreshNotifications()
called in both success callbacks, same pattern as user CRUD)
- Notification dropdown renders shield-x (red) for bans and
shield-check (green) for unbans
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Content-Security-Policy header (script-src 'self') blocked the inline <script> block that set window.ADMIN_ROLE, window.CURRENT_PAGE, and window.CSRF_TOKEN. This caused isSuperAdmin to always be false (hiding all edit/delete/reset buttons) and currentPage to default to 'dashboard' (keeping the Fail2Ban page stuck on the loading spinner). Moved the three window variables into js/config.php, a PHP-served external script that satisfies the 'self' CSP directive. Also added filemtime-based cache busting to grids.js and index.css. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ocked by apache2 sandboxing; simplify Client Config editor to a single tab - install.sh matched the wrong placeholder (xxx.xxx.xxx.xxx) against the actual template placeholder (10.10.100.27), so the public IP/hostname was never substituted into shipped .ovpn files. - Ubuntu 26.04+'s apache2.service hides /etc/sudoers(.d) from Apache and everything it spawns, breaking the web UI's sudo-based fail2ban-client and easyrsa calls no matter what's in sudoers.d. install.sh now installs a systemd override that restores visibility of sudoers for this unit only, when the packaged unit has the restriction. - Client Configuration Editor: removed the GNU/Linux and macOS/Viscosity tabs, keeping only the shared .ovpn template under a single "Editor" tab. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… sudo fix, Config editor) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment out the lz4-v2 compression setting and enable default compression.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
It would be nice to change the client.ovpn file name to "username".ovpn