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.
124 lines
4.4 KiB
Go
124 lines
4.4 KiB
Go
package util
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/labstack/gommon/log"
|
|
)
|
|
|
|
// Runtime config
|
|
var (
|
|
DisableLogin bool
|
|
BindAddress string
|
|
SmtpHostname string
|
|
SmtpPort int
|
|
SmtpUsername string
|
|
SmtpPassword string
|
|
SmtpNoTLSCheck bool
|
|
SmtpEncryption string
|
|
SmtpAuthType string
|
|
SmtpHelo string
|
|
SendgridApiKey string
|
|
EmailFrom string
|
|
EmailFromName string
|
|
SessionSecret [64]byte
|
|
SessionMaxDuration int64
|
|
WgConfTemplate string
|
|
BasePath string
|
|
SubnetRanges map[string]([]*net.IPNet)
|
|
SubnetRangesOrder []string
|
|
)
|
|
|
|
const (
|
|
DefaultUsername = "admin"
|
|
DefaultPassword = "admin"
|
|
DefaultIsAdmin = true
|
|
DefaultServerAddress = "10.252.1.0/24"
|
|
DefaultServerPort = 51820
|
|
DefaultDNS = "1.1.1.1"
|
|
DefaultMTU = 1450
|
|
DefaultPersistentKeepalive = 15
|
|
DefaultFirewallMark = "0xca6c" // i.e. 51820
|
|
DefaultTable = "auto"
|
|
DefaultConfigFilePath = "/etc/wireguard/wg0.conf"
|
|
// DefaultServerID is the server ID used by every route/handler that
|
|
// isn't explicitly scoped to a server (i.e. the legacy bare routes),
|
|
// and the ID a pre-multi-server install is migrated to.
|
|
DefaultServerID = "wg0"
|
|
UsernameEnvVar = "WGUI_USERNAME"
|
|
PasswordEnvVar = "WGUI_PASSWORD"
|
|
PasswordFileEnvVar = "WGUI_PASSWORD_FILE"
|
|
PasswordHashEnvVar = "WGUI_PASSWORD_HASH"
|
|
PasswordHashFileEnvVar = "WGUI_PASSWORD_HASH_FILE"
|
|
FaviconFilePathEnvVar = "WGUI_FAVICON_FILE_PATH"
|
|
EndpointAddressEnvVar = "WGUI_ENDPOINT_ADDRESS"
|
|
DNSEnvVar = "WGUI_DNS"
|
|
MTUEnvVar = "WGUI_MTU"
|
|
PersistentKeepaliveEnvVar = "WGUI_PERSISTENT_KEEPALIVE"
|
|
FirewallMarkEnvVar = "WGUI_FIREWALL_MARK"
|
|
TableEnvVar = "WGUI_TABLE"
|
|
ConfigFilePathEnvVar = "WGUI_CONFIG_FILE_PATH"
|
|
LogLevel = "WGUI_LOG_LEVEL"
|
|
ServerAddressesEnvVar = "WGUI_SERVER_INTERFACE_ADDRESSES"
|
|
ServerListenPortEnvVar = "WGUI_SERVER_LISTEN_PORT"
|
|
ServerPostUpScriptEnvVar = "WGUI_SERVER_POST_UP_SCRIPT"
|
|
ServerPostDownScriptEnvVar = "WGUI_SERVER_POST_DOWN_SCRIPT"
|
|
DefaultClientAllowedIpsEnvVar = "WGUI_DEFAULT_CLIENT_ALLOWED_IPS"
|
|
DefaultClientExtraAllowedIpsEnvVar = "WGUI_DEFAULT_CLIENT_EXTRA_ALLOWED_IPS"
|
|
DefaultClientUseServerDNSEnvVar = "WGUI_DEFAULT_CLIENT_USE_SERVER_DNS"
|
|
DefaultClientEnableAfterCreationEnvVar = "WGUI_DEFAULT_CLIENT_ENABLE_AFTER_CREATION"
|
|
)
|
|
|
|
func ParseBasePath(basePath string) string {
|
|
if !strings.HasPrefix(basePath, "/") {
|
|
basePath = "/" + basePath
|
|
}
|
|
if strings.HasSuffix(basePath, "/") {
|
|
basePath = strings.TrimSuffix(basePath, "/")
|
|
}
|
|
return basePath
|
|
}
|
|
|
|
func ParseSubnetRanges(subnetRangesStr string) map[string]([]*net.IPNet) {
|
|
subnetRanges := map[string]([]*net.IPNet){}
|
|
if subnetRangesStr == "" {
|
|
return subnetRanges
|
|
}
|
|
cidrSet := map[string]bool{}
|
|
subnetRangesStr = strings.TrimSpace(subnetRangesStr)
|
|
subnetRangesStr = strings.Trim(subnetRangesStr, ";:,")
|
|
ranges := strings.Split(subnetRangesStr, ";")
|
|
for _, rng := range ranges {
|
|
rng = strings.TrimSpace(rng)
|
|
rngSpl := strings.Split(rng, ":")
|
|
if len(rngSpl) != 2 {
|
|
log.Warnf("Unable to parse subnet range: %v. Skipped.", rng)
|
|
continue
|
|
}
|
|
rngName := strings.TrimSpace(rngSpl[0])
|
|
subnetRanges[rngName] = make([]*net.IPNet, 0)
|
|
cidrs := strings.Split(rngSpl[1], ",")
|
|
for _, cidr := range cidrs {
|
|
cidr = strings.TrimSpace(cidr)
|
|
_, net, err := net.ParseCIDR(cidr)
|
|
if err != nil {
|
|
log.Warnf("[%v] Unable to parse CIDR: %v. Skipped.", rngName, cidr)
|
|
continue
|
|
}
|
|
if cidrSet[net.String()] {
|
|
log.Warnf("[%v] CIDR already exists: %v. Skipped.", rngName, net.String())
|
|
continue
|
|
}
|
|
cidrSet[net.String()] = true
|
|
subnetRanges[rngName] = append(subnetRanges[rngName], net)
|
|
}
|
|
if len(subnetRanges[rngName]) == 0 {
|
|
delete(subnetRanges, rngName)
|
|
} else {
|
|
SubnetRangesOrder = append(SubnetRangesOrder, rngName)
|
|
}
|
|
}
|
|
return subnetRanges
|
|
}
|