From 7488f283c462d7dcc26dcd9dd6cb0bc9b1610e4a Mon Sep 17 00:00:00 2001 From: Cameron <113076756+cameronaw13@users.noreply.github.com> Date: Fri, 11 Aug 2023 01:25:56 -0700 Subject: [PATCH 01/12] secure jsondb user perms (#404) --- store/jsondb/jsondb.go | 37 ++++++++++++++++++++++++------ store/jsondb/jsondb_wake_on_lan.go | 9 +++++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index cb831ca..0b674b0 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -68,6 +68,7 @@ func (o *JsonDB) Init() error { serverInterface.PostDown = util.LookupEnvOrString(util.ServerPostDownScriptEnvVar, "") serverInterface.UpdatedAt = time.Now().UTC() o.conn.Write("server", "interfaces", serverInterface) + os.Chmod(serverInterfacePath, 0600) } // server's key pair @@ -82,6 +83,7 @@ func (o *JsonDB) Init() error { serverKeyPair.PublicKey = key.PublicKey().String() serverKeyPair.UpdatedAt = time.Now().UTC() o.conn.Write("server", "keypair", serverKeyPair) + os.Chmod(serverKeyPairPath, 0600) } // global settings @@ -106,14 +108,16 @@ func (o *JsonDB) Init() error { globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath) globalSetting.UpdatedAt = time.Now().UTC() o.conn.Write("server", "global_settings", globalSetting) + os.Chmod(globalSettingPath, 0600) } - + // hashes if _, err := os.Stat(hashesPath); os.IsNotExist(err) { clientServerHashes := new(model.ClientServerHashes) clientServerHashes.Client = "none" clientServerHashes.Server = "none" o.conn.Write("server", "hashes", clientServerHashes) + os.Chmod(hashesPath, 0600) } // user info @@ -132,6 +136,7 @@ func (o *JsonDB) Init() error { user.PasswordHash = hash } o.conn.Write("users", user.Username, user) + os.Chmod(path.Join(path.Join(o.dbPath, "users"), user.Username+".json"), 0600) } return nil @@ -175,7 +180,10 @@ func (o *JsonDB) GetUserByName(username string) (model.User, error) { // SaveUser func to save user in the database func (o *JsonDB) SaveUser(user model.User) error { - return o.conn.Write("users", user.Username, user) + userPath := path.Join(path.Join(o.dbPath, "users"), user.Username+".json") + output := o.conn.Write("users", user.Username, user) + os.Chmod(userPath, 0600) + return output } // DeleteUser func to remove user from the database @@ -285,7 +293,10 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti } func (o *JsonDB) SaveClient(client model.Client) error { - return o.conn.Write("clients", client.ID, client) + clientPath := path.Join(path.Join(o.dbPath, "clients"), client.ID+".json") + output := o.conn.Write("clients", client.ID, client) + os.Chmod(clientPath, 0600) + return output } func (o *JsonDB) DeleteClient(clientID string) error { @@ -293,15 +304,24 @@ func (o *JsonDB) DeleteClient(clientID string) error { } func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { - return o.conn.Write("server", "interfaces", serverInterface) + serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json") + output := o.conn.Write("server", "interfaces", serverInterface) + os.Chmod(serverInterfacePath, 0600) + return output } func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { - return o.conn.Write("server", "keypair", serverKeyPair) + serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json") + output := o.conn.Write("server", "keypair", serverKeyPair) + os.Chmod(serverKeyPairPath, 0600) + return output } func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { - return o.conn.Write("server", "global_settings", globalSettings) + globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json") + output := o.conn.Write("server", "global_settings", globalSettings) + os.Chmod(globalSettingsPath, 0600) + return output } func (o *JsonDB) GetPath() string { @@ -314,5 +334,8 @@ func (o *JsonDB) GetHashes() (model.ClientServerHashes, error) { } func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error { - return o.conn.Write("server", "hashes", hashes) + hashesPath := path.Join(path.Join(o.dbPath, "server"), "hashes.json") + output := o.conn.Write("server", "hashes", hashes) + os.Chmod(hashesPath, 0600) + return output } diff --git a/store/jsondb/jsondb_wake_on_lan.go b/store/jsondb/jsondb_wake_on_lan.go index e492aa8..a0ee935 100644 --- a/store/jsondb/jsondb_wake_on_lan.go +++ b/store/jsondb/jsondb_wake_on_lan.go @@ -3,6 +3,9 @@ package jsondb import ( "encoding/json" "fmt" + "os" + "path" + "github.com/ngoduykhanh/wireguard-ui/model" ) @@ -65,7 +68,11 @@ func (o *JsonDB) SaveWakeOnLanHost(host model.WakeOnLanHost) error { return err } - return o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host) + wakeOnLanHostPath := path.Join(path.Join(o.dbPath, model.WakeOnLanHostCollectionName), resourceName+".json") + output := o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host) + os.Chmod(wakeOnLanHostPath, 0600) + return output + } func (o *JsonDB) DeleteWakeOnHost(host model.WakeOnLanHost) error { From 364a43e3dcc5a9598827a166036346be8ce08094 Mon Sep 17 00:00:00 2001 From: Paul Dee Date: Fri, 11 Aug 2023 10:34:11 +0200 Subject: [PATCH 02/12] Implement updating a client Pub+PSK when editing a client (#401) This covers the normal use-case where clients generate keys locally on their device and notify the server of their new/updated keys. The server verifies Preshared and Public keys independently of each other. Should a client generate a new tunnel which lacks a PSK and send only a Public key to the server (admin) where the earlier server created profile has a Preshared key, the server admin/user must determine the course of action: keep or remove the PSK. --- handler/routes.go | 41 +++++++++++++++++++++++++++++++++++++++++ templates/clients.html | 30 +++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/handler/routes.go b/handler/routes.go index e443af9..a2ceeaf 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -567,6 +567,45 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Extra Allowed IPs must be in CIDR format"}) } + // update Wireguard Client PublicKey + if client.PublicKey != _client.PublicKey && _client.PublicKey != "" { + _, err := wgtypes.ParseKey(_client.PublicKey) + if err != nil { + log.Error("Cannot verify provided Wireguard public key: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify provided Wireguard public key"}) + } + // check for duplicates + clients, err := db.GetClients(false) + if err != nil { + log.Error("Cannot get client list for duplicate public key check") + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get client list for duplicate public key check"}) + } + for _, other := range clients { + if other.Client.PublicKey == _client.PublicKey { + log.Error("Duplicate Public Key") + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Duplicate Public Key"}) + } + } + + // When replacing any PublicKey, discard any locally stored Wireguard Client PrivateKey + // Client PubKey no longer corresponds to locally stored PrivKey. + // QR code (needs PrivateKey) for this client is no longer possible now. + + if client.PrivateKey != "" { + client.PrivateKey = "" + } + + } + + // update Wireguard Client PresharedKey + if client.PresharedKey != _client.PresharedKey && _client.PresharedKey != "" { + _, err := wgtypes.ParseKey(_client.PresharedKey) + if err != nil { + log.Error("Cannot verify provided Wireguard preshared key: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify provided Wireguard preshared key"}) + } + } + // map new data client.Name = _client.Name client.Email = _client.Email @@ -575,6 +614,8 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { client.AllocatedIPs = _client.AllocatedIPs client.AllowedIPs = _client.AllowedIPs client.ExtraAllowedIPs = _client.ExtraAllowedIPs + client.PublicKey = _client.PublicKey + client.PresharedKey = _client.PresharedKey client.UpdatedAt = time.Now().UTC() // write to the database diff --git a/templates/clients.html b/templates/clients.html index 94ab634..bcd5855 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -129,6 +129,26 @@ Wireguard Clients +
+ Public and Preshared Keys + + + +
+ + +
+
+ + +
+
+
+ + +
@@ -413,6 +417,7 @@ const email = $("#client_email").val(); const allocated_ips = $("#client_allocated_ips").val().split(","); const allowed_ips = $("#client_allowed_ips").val().split(","); + const endpoint = $("#client_endpoint").val(); let use_server_dns = false; let extra_allowed_ips = []; @@ -434,7 +439,7 @@ const preshared_key = $("#client_preshared_key").val(); const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips, - "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, + "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; $.ajax({ diff --git a/templates/clients.html b/templates/clients.html index bcd5855..ae66ecf 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -113,6 +113,10 @@ Wireguard Clients
+
+ + +
@@ -477,6 +481,8 @@ Wireguard Clients modal.find("#_client_extra_allowed_ips").addTag(obj); }); + modal.find("#_client_endpoint").val(client.endpoint); + modal.find("#_use_server_dns").prop("checked", client.use_server_dns); modal.find("#_enabled").prop("checked", client.enabled); @@ -564,6 +570,8 @@ Wireguard Clients extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(","); } + const endpoint = $("#_client_endpoint").val(); + if ($("#_use_server_dns").is(':checked')){ use_server_dns = true; } @@ -575,7 +583,8 @@ Wireguard Clients } const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips, - "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; + "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, + "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; $.ajax({ cache: false, diff --git a/templates/wg.conf b/templates/wg.conf index 81893a8..6b216ce 100644 --- a/templates/wg.conf +++ b/templates/wg.conf @@ -20,6 +20,7 @@ Table = {{ .globalSettings.Table }} # Update at: {{ .Client.UpdatedAt }} [Peer] PublicKey = {{ .Client.PublicKey }} -{{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }} -{{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}} -{{end}}{{end}} +{{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }}{{end}} +AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}}{{end}} +{{if .Client.Endpoint }}Endpoint = {{ .Client.Endpoint }}{{end}} +{{end}} From af7742bfb32bffef98e861dd0eda67f83dabe569 Mon Sep 17 00:00:00 2001 From: Michael Walter <40073144+MiguSchweiz@users.noreply.github.com> Date: Mon, 25 Dec 2023 20:22:42 +0100 Subject: [PATCH 09/12] Update routes.go (#475) use config file download mime type "txt/conf" to prevent downloaded configs being saved as .txt, instead of wanted .conf. Tested on Android Firefox and Chrome --- handler/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/routes.go b/handler/routes.go index 3d1982f..aa5461b 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -729,7 +729,7 @@ func DownloadClient(db store.IStore) echo.HandlerFunc { // set response header for downloading c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s.conf", clientData.Client.Name)) - return c.Stream(http.StatusOK, "text/plain", reader) + return c.Stream(http.StatusOK, "text/conf", reader) } } From c8623082fec8997ad8a933de79d0958b69a3df02 Mon Sep 17 00:00:00 2001 From: nebulosa2007 <85841412+nebulosa2007@users.noreply.github.com> Date: Mon, 25 Dec 2023 22:23:51 +0300 Subject: [PATCH 10/12] Make Interface PreDown setting. (#480) --- model/server.go | 1 + templates/server.html | 9 ++++++++- templates/wg.conf | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/model/server.go b/model/server.go index 0784eea..0aa804f 100644 --- a/model/server.go +++ b/model/server.go @@ -23,5 +23,6 @@ type ServerInterface struct { ListenPort int `json:"listen_port,string"` // ,string to get listen_port string input as int UpdatedAt time.Time `json:"updated_at"` PostUp string `json:"post_up"` + PreDown string `json:"pre_down"` PostDown string `json:"post_down"` } diff --git a/templates/server.html b/templates/server.html index 366d301..6a24e6e 100644 --- a/templates/server.html +++ b/templates/server.html @@ -42,6 +42,12 @@ Wireguard Server Settings
+
+ + +
+
Date: Tue, 26 Dec 2023 00:25:38 +0500 Subject: [PATCH 11/12] Fixed tag input being too small and unable to fit a CIDR (#483) Co-authored-by: 0xCA --- templates/base.html | 3 +++ templates/clients.html | 3 +++ templates/global_settings.html | 1 + templates/server.html | 1 + 4 files changed, 8 insertions(+) diff --git a/templates/base.html b/templates/base.html index 88d7568..c2fa367 100644 --- a/templates/base.html +++ b/templates/base.html @@ -497,6 +497,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -508,6 +509,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -518,6 +520,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); diff --git a/templates/clients.html b/templates/clients.html index ae66ecf..8b4e4ab 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -427,6 +427,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -438,6 +439,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -448,6 +450,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace' : true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }) diff --git a/templates/global_settings.html b/templates/global_settings.html index cafb630..73b3c93 100644 --- a/templates/global_settings.html +++ b/templates/global_settings.html @@ -203,6 +203,7 @@ Global Settings 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); diff --git a/templates/server.html b/templates/server.html index 6a24e6e..e1116a6 100644 --- a/templates/server.html +++ b/templates/server.html @@ -167,6 +167,7 @@ Wireguard Server Settings 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); From e73047b14fbcf9799e00641991ba092fb0c3becd Mon Sep 17 00:00:00 2001 From: Vahid Date: Mon, 25 Dec 2023 23:01:11 +0330 Subject: [PATCH 12/12] Feature: Unix domain socket support (#492) Co-authored-by: Khanh Ngo --- .gitignore | 3 +++ README.md | 2 +- main.go | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f1e3f44..610b977 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ node_modules/ .vscode .idea +# Vim +.*.sw[op] + # Examples examples/docker-compose/config examples/docker-compose/db diff --git a/README.md b/README.md index f7c41f8..d585868 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ docker-compose up | Variable | Description | Default | |-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------| | `BASE_PATH` | Set this variable if you run wireguard-ui under a subpath of your reverse proxy virtual host (e.g. /wireguard) | N/A | -| `BIND_ADDRESS` | The addresses that can access to the web interface and the port | 0.0.0.0:80 | +| `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 | | `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A | | `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A | | `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` | diff --git a/main.go b/main.go index b226ccc..fd4bc90 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,9 @@ import ( "net/http" "os" "time" + "strings" + "net" + "syscall" "github.com/labstack/echo/v4" "github.com/labstack/gommon/log" @@ -229,7 +232,20 @@ func main() { // serves other static files app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler))) - app.Logger.Fatal(app.Start(util.BindAddress)) + if strings.HasPrefix(util.BindAddress, "unix://") { + // Listen on unix domain socket. + // https://github.com/labstack/echo/issues/830 + syscall.Unlink(util.BindAddress[6:]) + l, err := net.Listen("unix", util.BindAddress[6:]) + if err != nil { + app.Logger.Fatal(err) + } + app.Listener = l + app.Logger.Fatal(app.Start("")) + } else { + // Listen on TCP socket + app.Logger.Fatal(app.Start(util.BindAddress)) + } } func initServerConfig(db store.IStore, tmplDir fs.FS) {