diff --git a/.env b/.env index 0533cb7..9a82417 100644 --- a/.env +++ b/.env @@ -1,6 +1,12 @@ SERVER=0.0.0.0 PORT=8080 -GIN_MODE=debug +GIN_MODE=release WG_CONF_DIR=./wireguard WG_INTERFACE_NAME=wg0.conf + +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USERNAME=account@gmail.com +SMTP_PASSWORD="*************" +SMTP_FROM="Wg Gen Web " diff --git a/README.md b/README.md index 9a563ec..74b424f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Wg Gen Web -Simple Web based configuration generator for [WireGuard](https://wireguard.com). +

Simple Web based configuration generator for WireGuard

---- +Simple Web based configuration generator for [WireGuard](https://wireguard.com). [![pipeline status](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/badges/master/pipeline.svg)](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/commits/master) [![Go Report Card](https://goreportcard.com/badge/github.com/vx3r/wg-gen-web)](https://goreportcard.com/report/github.com/vx3r/wg-gen-web) @@ -30,10 +30,12 @@ The goal is to run Wg Gen Web in a container and WireGuard on host system. * Generation of `wg0.conf` after any modification * Dockerized * Pretty cool look -![Screenshot](Wg-Gen-Web.png) +![Screenshot](wg-gen-web_screenshot.png) ## Running +### Docker + The easiest way to run Wg Gen Web is using the container image ``` docker run --rm -it -v /tmp/wireguard:/data -p 8080:8080 -e "WG_CONF_DIR=/data" vx3r/wg-gen-web:latest @@ -51,6 +53,11 @@ services: environment: - WG_CONF_DIR=/data - WG_INTERFACE_NAME=wg0.conf + - SMTP_HOST=smtp.gmail.com + - SMTP_PORT=587 + - SMTP_USERNAME=account@gmail.com + - SMTP_PASSWORD="*************" + - SMTP_FROM="Wg Gen Web " volumes: - /etc/wireguard:/data ``` @@ -58,7 +65,18 @@ Please note that mapping ```/etc/wireguard``` to ```/data``` inside the docker, If needed, please make sure to backup your files from ```/etc/wireguard```. A workaround would be to change the ```WG_INTERFACE_NAME``` to something different, as it will create a new interface (```wg-auto.conf``` for example), note that if you do so, you will have to adapt your daemon accordingly. -### Automatically apply changes using ```systemd``` + +### Directly without docker + +Fill free to download latest artefacts from my GitLab server: +* [Backend](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/-/jobs/artifacts/master/download?job=build-front) +* [Frontend](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/-/jobs/artifacts/master/download?job=build-back) + +Put everything in one directory, create `.env` file with all configurations and run the backend. + +## Automatically apply changes to WireGuard + +### Using ```systemd``` Using `systemd.path` monitor for directory changes see [systemd doc](https://www.freedesktop.org/software/systemd/man/systemd.path.html) ``` # /etc/systemd/system/wg-gen-web.path @@ -87,7 +105,7 @@ WantedBy=multi-user.target ``` Which will restart WireGuard service -### Automatically apply changes using ```inotifywait``` +### Using ```inotifywait``` For any other init system, create a daemon running this script ``` #!/bin/sh @@ -111,8 +129,8 @@ Feel free to modify this file in order to use your existing keys ## TODO * Multi-user support behind [Authelia](https://github.com/authelia/authelia) (suggestions / thoughts are welcome) - * Send configs by email to client - + * ~~Send configs by email to client~~ + ## License * Do What the Fuck You Want to Public License. [LICENSE-WTFPL](LICENSE-WTFPL) or http://www.wtfpl.net \ No newline at end of file diff --git a/Wg-Gen-Web.png b/Wg-Gen-Web.png deleted file mode 100644 index e3382b9..0000000 Binary files a/Wg-Gen-Web.png and /dev/null differ diff --git a/api/api.go b/api/api.go index 9978adb..3aa4d6c 100644 --- a/api/api.go +++ b/api/api.go @@ -20,6 +20,7 @@ func ApplyRoutes(r *gin.Engine) { client.DELETE("/:id", deleteClient) client.GET("", readClients) client.GET("/:id/config", configClient) + client.GET("/:id/email", emailClient) } server := r.Group("/api/v1.0/server") @@ -149,6 +150,21 @@ func configClient(c *gin.Context) { return } +func emailClient(c *gin.Context) { + id := c.Param("id") + + err := repository.EmailClient(id) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to send email to client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, gin.H{}) +} + func readServer(c *gin.Context) { client, err := repository.ReadServer() if err != nil { diff --git a/go.mod b/go.mod index 5ab8ea8..1bf44d9 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,6 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) diff --git a/go.sum b/go.sum index 682d047..554bbe9 100644 --- a/go.sum +++ b/go.sum @@ -98,11 +98,15 @@ golang.zx2c4.com/wireguard v0.0.20191012/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49 golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb h1:EZFZIHfDUPApqlA2wgF5LBAXKIKAxNckrehUTPYYAHc= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb/go.mod h1:vpFXH8L2bfaEJ/8I7DZ0CVOHsVydo6KeW9Iqh3qMa4g= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index f601168..9127ee2 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,10 @@ func main() { } } - if os.Getenv("GIN_MODE") == "release" { + if os.Getenv("GIN_MODE") == "debug" { + // set gin release mode + gin.SetMode(gin.DebugMode) + } else { // set gin release mode gin.SetMode(gin.ReleaseMode) // disable console color diff --git a/repository/repository.go b/repository/repository.go index 193abec..f52cd1f 100644 --- a/repository/repository.go +++ b/repository/repository.go @@ -5,13 +5,16 @@ import ( "errors" uuid "github.com/satori/go.uuid" log "github.com/sirupsen/logrus" + "github.com/skip2/go-qrcode" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "gopkg.in/gomail.v2" "io/ioutil" "os" "path/filepath" "sort" + "strconv" "strings" "time" ) @@ -157,6 +160,82 @@ func DeleteClient(id string) error { return generateWgConfig() } +// SendEmail to client +func EmailClient(id string) error { + client, err := ReadClient(id) + if err != nil { + return err + } + + configData, err := ReadClientConfig(id) + if err != nil { + return err + } + + // conf as .conf file + tmpfileCfg, err := ioutil.TempFile("", "wireguard-vpn-*.conf") + if err != nil { + return err + } + if _, err := tmpfileCfg.Write(configData); err != nil { + return err + } + if err := tmpfileCfg.Close(); err != nil { + return err + } + defer os.Remove(tmpfileCfg.Name()) // clean up + + // conf as png image + png, err := qrcode.Encode(string(configData), qrcode.Medium, 280) + if err != nil { + return err + } + tmpfilePng, err := ioutil.TempFile("", "qrcode-*.png") + if err != nil { + return err + } + if _, err := tmpfilePng.Write(png); err != nil { + return err + } + if err := tmpfilePng.Close(); err != nil { + return err + } + defer os.Remove(tmpfilePng.Name()) // clean up + + // get email body + emailBody, err := util.DumpEmail(client, filepath.Base(tmpfilePng.Name())) + if err != nil { + return err + } + + // port to int + port, err := strconv.Atoi(os.Getenv("SMTP_PORT")) + if err != nil { + return err + } + + d := gomail.NewDialer(os.Getenv("SMTP_HOST"), port, os.Getenv("SMTP_USERNAME"), os.Getenv("SMTP_PASSWORD")) + s, err := d.Dial() + if err != nil { + return err + } + m := gomail.NewMessage() + + m.SetHeader("From", os.Getenv("SMTP_FROM")) + m.SetAddressHeader("To", client.Email, client.Name) + m.SetHeader("Subject", "WireGuard VPN Configuration") + m.SetBody("text/html", emailBody.String()) + m.Attach(tmpfileCfg.Name()) + m.Embed(tmpfilePng.Name()) + + err = gomail.Send(s, m) + if err != nil { + return err + } + + return nil +} + // ReadClients all clients func ReadClients() ([]*model.Client, error) { clients := make([]*model.Client, 0) diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico deleted file mode 100644 index df36fcf..0000000 Binary files a/ui/public/favicon.ico and /dev/null differ diff --git a/ui/public/favicon.png b/ui/public/favicon.png new file mode 100644 index 0000000..31b8281 Binary files /dev/null and b/ui/public/favicon.png differ diff --git a/ui/public/index.html b/ui/public/index.html index b1e2b42..cffd763 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -4,7 +4,7 @@ - + ui diff --git a/ui/src/App.vue b/ui/src/App.vue index 2ed1d11..753e268 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -2,6 +2,7 @@ + Wg Gen Web diff --git a/ui/src/assets/logo.png b/ui/src/assets/logo.png index f3d2503..67dc535 100644 Binary files a/ui/src/assets/logo.png and b/ui/src/assets/logo.png differ diff --git a/ui/src/assets/logo.svg b/ui/src/assets/logo.svg deleted file mode 100644 index 145b6d1..0000000 --- a/ui/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ -Artboard 46 diff --git a/ui/src/views/Home.vue b/ui/src/views/Home.vue index b5b611f..b2f9424 100644 --- a/ui/src/views/Home.vue +++ b/ui/src/views/Home.vue @@ -13,15 +13,15 @@ @@ -47,12 +47,12 @@ @@ -78,10 +78,12 @@ Update server configuration + mdi-update @@ -100,6 +102,7 @@ @click.stop="dialogAddClient = true" > Add new client + mdi-account-multiple-plus-outline @@ -146,8 +149,8 @@ text :href="getUrlToConfig(client.id, false)" > - Download configuration - mdi-cloud-download + Download + mdi-cloud-download-outline mdi-trash-can-outline + + Send email + mdi-email-send-outline + - Enable or disable this client + {{client.enable ? 'Disable' : 'Enable'}} this client @@ -407,6 +417,14 @@ }); } }, + sendEmailClient(id) { + this.$get(`/client/${id}/email`).then((res) => { + this.notify('success', "Email successfully sent"); + this.getData() + }).catch((e) => { + this.notify('error', e.response.status + ' ' + e.response.statusText); + }); + }, getUrlToConfig(id, qrcode){ let base = "/api/v1.0"; if (process.env.NODE_ENV === "development"){ diff --git a/util/tpl.go b/util/tpl.go index a92c6b4..8733839 100644 --- a/util/tpl.go +++ b/util/tpl.go @@ -8,6 +8,192 @@ import ( ) var ( + emailTpl = ` + + + + + + + + + + + + + + + Email Template + + + + + + + + + + +
+ + + + +
+ + + + + + +
+ + + + +
+ + + + + + +
+ + + + +
+
+ + + + + + + +
Hello
You probably requested VPN configuration. Here is {{.Client.Name}} configuration created {{.Client.Created.Format "Jan 02, 2006 15:04:05 UTC"}}. Scan the Qrcode or open attached configuration file in VPN client.
+
+
+
+ + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + + + + + + +
About WireGuard
WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.
+ + + + +
Download WireGuard VPN Client
+
+
+
+
+ + + + + + + +
+ + + + + + + +
Wg Gen Web - Simple Web based configuration generator for WireGuard
More info on Github
+
+ +
+
+ + +` + clientTpl = ` [Interface] Address = {{.Client.Address}} @@ -78,6 +264,24 @@ func DumpServerWg(clients []*model.Client, server *model.Server) (bytes.Buffer, }) } +// DumpEmail dump server wg config with go template +func DumpEmail(client *model.Client, qrcodePngName string) (bytes.Buffer, error) { + var tplBuff bytes.Buffer + + t, err := template.New("email").Parse(emailTpl) + if err != nil { + return tplBuff, err + } + + return dump(t, struct { + Client *model.Client + QrcodePngName string + }{ + Client: client, + QrcodePngName: qrcodePngName, + }) +} + func dump(tpl *template.Template, data interface{}) (bytes.Buffer, error) { var tplBuff bytes.Buffer diff --git a/wg-gen-web_cover.png b/wg-gen-web_cover.png new file mode 100644 index 0000000..d1e66cc Binary files /dev/null and b/wg-gen-web_cover.png differ diff --git a/wg-gen-web_screenshot.png b/wg-gen-web_screenshot.png new file mode 100644 index 0000000..667c7a1 Binary files /dev/null and b/wg-gen-web_screenshot.png differ