Commit Graph
12 Commits
Author SHA1 Message Date
sysopsandClaude Sonnet 5 0dbb916866 Fix nil-pointer crash generating QR codes for non-default servers
GetClients, GetClientByID, and SendRequestedConfigsToTelegram all
looked up the legacy "wg0" server unconditionally when building a
client's QR code / config, ignoring which server the client actually
belongs to. On any multi-server setup without a migrated wg0, the
server lookup returned a zero-value model.Server (error discarded),
and BuildClientConfig then dereferenced its nil KeyPair pointer,
panicking whenever a client's QR code was rendered.

Now looks up the client's own ServerID (falling back to wg0 only for
legacy clients with no ServerID set) and surfaces the lookup error
instead of silently continuing with an empty server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:42:36 +02:00
sysopsandClaude Sonnet 5 fc0d192e59 Port upstream security fixes and features from ngoduykhanh/wireguard-ui
- Escape HTML in client list and wake-on-LAN names to prevent XSS
- Log successful/failed login attempts with remote address
- Fix leading-comma bug in AllowedIPs template when only extra allowed IPs are set
- Add PreUp script support for server interfaces (alongside existing PostUp/PreDown/PostDown)
- Fix endpoint parsing to support IPv6 addresses (upstream PR #223)

Cherry-picked from upstream PRs #656, #653, #680, #673, #223.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:26:30 +02:00
sysopsandClaude Sonnet 5 0f92317abb Fix "no such file or directory" on first use of new collections
scribble.ReadAll only returns its own ErrMissingCollection when the
collection name is empty - when the collection's directory simply
doesn't exist yet (e.g. before the first firewall rule or IP list entry
is ever created), it returns a raw os.IsNotExist error instead, which
GetFirewallRules/GetIPListEntries/GetServers didn't handle, surfacing as
"open db/ip_list_entries: no such file or directory" on a fresh install.
New isMissingCollectionErr() helper treats both cases as "empty
collection", not a real failure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:55:37 +02:00
sysopsandClaude Sonnet 5 eb1913d400 Add central host-wide firewall allow/block lists
New model.IPListEntry + jsondb CRUD, independent of any single WireGuard
server. firewall.GenerateGlobalRuleset builds an nftables table
(wireguard_ui_global) with allow/block sets evaluated at priority -10 -
before every per-server table - so it applies to all traffic on the host,
not just WireGuard. Allow entries always win over block entries.
firewall.ApplyGlobal loads it live via `nft -f`, scoped to that one table.

New "Global Firewall Lists" page (nav entry under Settings): add/delete
entries, ruleset preview, "Apply now (live)" with an explicit confirm()
warning since this affects the whole host's firewall, not just one server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:45:53 +02:00
sysopsandClaude Sonnet 5 1d080904b0 Add live firewall rule management per server (nftables)
New model.FirewallRule + jsondb CRUD (GetFirewallRules/CreateFirewallRule/
UpdateFirewallRule/DeleteFirewallRule), scoped per server. firewall package
now generates a full ruleset (baseline + enabled custom rules) and can
apply it live via `nft -f` (firewall.Apply), scoped to a per-server
nftables table (wireguard_ui_<serverID>) so applying one server never
touches another server's rules or any pre-existing firewall state.

New endpoints: GET/POST /servers/:id/firewall/rules, POST .../rules/:ruleId,
POST .../rules/:ruleId/delete, POST .../apply (live, admin-only). UI in the
All Servers page: rule table with add/delete, ruleset preview, and an
"Apply now (live)" button with an explicit confirm() warning before it
touches the running firewall.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:31:08 +02:00
sysopsandClaude Sonnet 5 58db6839c3 Remove legacy default-server page, unify on multi-server registry
Deletes /wg-server (route, handlers, template) entirely and adds generic
/servers/:id/interface and /servers/:id/keypair endpoints + UI in the
All Servers page, so every server (including the default one) is managed
through the same per-server registry. Drops the write-through dual-write
hacks that kept the old single-server collection in sync - the registry
is now the single source of truth. One-time legacy-install migration path
is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 16:27:17 +02:00
sysops 74389a9d49 Scope client management and config-apply per server (step 4)
Real per-server data isolation, the core ask behind the access-control
work: clients, config generation, and the client-management UI are now
scoped by server ID instead of implicitly operating on one global
"the server".

- util.DefaultServerID ("wg0") is the server every legacy bare route
  now resolves to, so old and new routes share one consistent identity
  instead of drifting apart.
- New /servers/:id/... routes (new-client, update-client, remove-client,
  set-status, download, api/clients, api/client/:cid, api/apply-wg-config)
  reuse the same handlers as the legacy routes via resolveServerID(c),
  gated by RequireServerAccess middleware. Cross-server edits/deletes on
  scoped routes are rejected (403) if a client belongs to a different
  server.
- Fixes a real data leak: ApplyServerConfig previously wrote ALL clients
  from ALL servers into whichever single wg.conf it targeted. It now
  filters clients by server ID before generating a config, and resolves
  each server's own ConfigFilePath/EndpointAddress via the new
  ServerSetting record instead of the app-wide GlobalSetting.
- WireGuardServerInterfaces/WireGuardServerKeyPair/GlobalSettingSubmit
  (the legacy /wg-server and /global-settings edit routes) now write
  through to the new per-server registry record for "wg0" in addition
  to the legacy collection, so the two stay in sync until the legacy
  routes are eventually retired.
- New templates/server_clients.html: per-server clone of clients.html
  wired to the scoped endpoints, with a server name/id heading.
- base.html's shared "New Client" and "Apply Config" actions (used by
  every page's nav buttons) now target the scoped route when a
  serverID is present on the page, instead of always hitting the
  legacy default-server endpoint regardless of which server's client
  page is open.

Legacy bare routes (/, /new-client, /wg-server, ...) are untouched and
still fully functional against the default "wg0" server - nothing was
removed yet, per the incremental-delivery approach for this project.
2026-07-11 23:47:57 +02:00
sysops 5b72a7c118 Add per-server access control (User.ServerIDs)
Non-admin users are now restricted to servers explicitly listed in
their new ServerIDs field; empty means no access (secure by default).
Admins always have full access. Migration backfills existing users'
ServerIDs with the migrated legacy server so nobody is locked out on
upgrade. New RequireServerAccess middleware enforces this on
/servers/:id/... routes (applied to GET /servers/:id/clients so far);
GET /servers also filters its list for non-admins.
2026-07-11 23:20:52 +02:00
sysops a946c059c3 Add read-only multi-server API (step 2) + dedupe serverID validation
GET /servers lists registered servers; GET /servers/:id/clients returns
that server's client list (filtered in-handler, store.GetClients isn't
server-scoped yet - that's a later step). Old routes untouched.

Also factors the repeated serverID validation guard in jsondb.go's new
server-scoped methods into one validateServerID() helper, per a code
simplification review.
2026-07-11 22:41:10 +02:00
sysops 7eca2d01b8 Add server-registry store methods with record-ID validation (step 1)
New IStore methods for a per-server registry (GetServers, GetServerByID,
CreateServer, DeleteServer, GetServerSettings/SaveServerSettings,
GetServerHashes/SaveServerHashes), all additive - existing single-server
methods untouched. Adds util.ValidateRecordID/ValidateInterfaceName and
applies them to every new method taking a server ID, closing a path-
traversal gap before serverID is ever driven by user input (flagged by
a security review pass).
2026-07-11 22:39:53 +02:00
sysops 40d7862702 Add multi-server data model + legacy DB migration (step 0)
Adds Server.ID/Name, ServerInterface.Name, Client.ServerID, and a new
ServerSetting type as additive fields so existing single-server code
paths keep working unchanged. jsondb.Init() now detects a pre-existing
single-server db/server/ layout and mirrors it into new servers/,
server_settings/, server_hashes/ collections plus backfills ServerID
on existing clients, without touching/removing the legacy files yet.
2026-07-11 22:35:45 +02:00
sysopsandClaude Sonnet 5 867dc7740a Replace from-scratch rewrite with real ngoduykhanh/wireguard-ui fork
The from-scratch Go rewrite had unresolved bugs (missing go.sum, UI
404s, path issues) from being built without a working local Go
toolchain to verify against. Switching strategy: use the actual
upstream wireguard-ui codebase (proven, battle-tested single-server
manager) as the base, and extend it for multi-server support instead
of re-deriving everything from zero.

Kept our own installers (bootstrap.sh, update.sh, scripts/install.sh,
scripts/proxmox-install.sh) - these still apply, just need updating
to build/install the upstream module layout instead of the old
cmd/wireguard-ui-multi structure.

Module path intentionally left as upstream's own
(github.com/ngoduykhanh/wireguard-ui) for now to avoid touching every
internal import; revisit if this needs to be fully rebranded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 18:34:44 +02:00