From febf075f8d874ed2f844193517be8ae539715ea7 Mon Sep 17 00:00:00 2001 From: Khanh Ngo <k@ndk.name> Date: Sun, 19 Apr 2020 15:50:59 +0700 Subject: [PATCH] Add Server config page Handle server ip addresses input and store TODO: Key pair form --- client/khanh.json | 4 - handler/routes.go | 59 ++++++++- main.go | 5 +- model/server.go | 25 ++++ router/router.go | 3 +- templates/base.html | 18 ++- templates/{home.html => clients.html} | 6 +- templates/server.html | 165 ++++++++++++++++++++++++++ util/util.go | 22 +++- 9 files changed, 285 insertions(+), 22 deletions(-) delete mode 100644 client/khanh.json create mode 100644 model/server.go rename templates/{home.html => clients.html} (98%) create mode 100644 templates/server.html diff --git a/client/khanh.json b/client/khanh.json deleted file mode 100644 index ef5f7c7..0000000 --- a/client/khanh.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "privateKey": "xyz", - "pulbicKey": "123" -} \ No newline at end of file diff --git a/handler/routes.go b/handler/routes.go index 959a131..03a51b4 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -16,8 +16,8 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -// Home handler -func Home() echo.HandlerFunc { +// WireGuardClients handler +func WireGuardClients() echo.HandlerFunc { return func(c echo.Context) error { // initialize database directory dir := "./db" @@ -53,7 +53,7 @@ func Home() echo.HandlerFunc { clientDataList = append(clientDataList, clientData) } - return c.Render(http.StatusOK, "home.html", map[string]interface{}{ + return c.Render(http.StatusOK, "clients.html", map[string]interface{}{ "name": "Khanh", "clientDataList": clientDataList, }) @@ -68,7 +68,7 @@ func NewClient() echo.HandlerFunc { // validate the input AllowedIPs if util.ValidateAllowedIPs(client.AllowedIPs) == false { - log.Warn("Invalid Allowed IPs input from user: %v", client.AllowedIPs) + log.Warnf("Invalid Allowed IPs input from user: %v", client.AllowedIPs) return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Allowed IPs must be in CIDR format"}) } @@ -122,3 +122,54 @@ func RemoveClient() echo.HandlerFunc { return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Client removed"}) } } + +// WireGuardServer handler +func WireGuardServer() echo.HandlerFunc { + return func(c echo.Context) error { + + // initialize database directory + dir := "./db" + db, err := scribble.New(dir, nil) + if err != nil { + log.Error("Cannot initialize the database: ", err) + } + + serverInterface := model.ServerInterface{} + if err := db.Read("server", "interfaces", &serverInterface); err != nil { + log.Error("Cannot fetch server interface config from database: ", err) + } + + return c.Render(http.StatusOK, "server.html", map[string]interface{}{ + "name": "Khanh", + "serverInterface": serverInterface, + }) + } +} + +// WireGuardServerInterfaces handler +func WireGuardServerInterfaces() echo.HandlerFunc { + return func(c echo.Context) error { + serverInterface := new(model.ServerInterface) + c.Bind(serverInterface) + + // validate the input addresses + if util.ValidateServerAddresses(serverInterface.Addresses) == false { + log.Warnf("Invalid server interface addresses input from user: %v", serverInterface.Addresses) + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Interface IP addresses must be in CIDR format"}) + } + + serverInterface.UpdatedAt = time.Now().UTC() + + // write config to the database + dir := "./db" + db, err := scribble.New(dir, nil) + if err != nil { + log.Error("Cannot initialize the database: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"}) + } + db.Write("server", "interfaces", serverInterface) + log.Infof("Updated wireguard server interfaces settings: %v", serverInterface) + + return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated interface addresses successfully"}) + } +} diff --git a/main.go b/main.go index 95d1d20..3334bdb 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,10 @@ import ( func main() { app := router.New() - app.GET("/", handler.Home()) + app.GET("/", handler.WireGuardClients()) app.POST("/new-client", handler.NewClient()) app.POST("/remove-client", handler.RemoveClient()) - + app.GET("/wg-server", handler.WireGuardServer()) + app.POST("wg-server/interfaces", handler.WireGuardServerInterfaces()) app.Logger.Fatal(app.Start("127.0.0.1:5000")) } diff --git a/model/server.go b/model/server.go new file mode 100644 index 0000000..06ab21a --- /dev/null +++ b/model/server.go @@ -0,0 +1,25 @@ +package model + +import ( + "time" +) + +// Server model +type Server struct { + KeyPair *ServerKeypair + Interface *ServerInterface +} + +// ServerKeypair model +type ServerKeypair struct { + PrivateKey string `json:"private_key"` + PublicKey string `json:"pulbic_key"` + UpdatedAt time.Time `json:"updated_at"` +} + +// ServerInterface model +type ServerInterface struct { + Addresses []string `json:"addresses"` + ListenPort int `json:"listen_port,string"` // ,string to get listen_port string input as int + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/router/router.go b/router/router.go index d30d710..c2d6bcc 100644 --- a/router/router.go +++ b/router/router.go @@ -29,7 +29,8 @@ func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c func New() *echo.Echo { e := echo.New() templates := make(map[string]*template.Template) - templates["home.html"] = template.Must(template.ParseFiles("templates/home.html", "templates/base.html")) + templates["clients.html"] = template.Must(template.ParseFiles("templates/clients.html", "templates/base.html")) + templates["server.html"] = template.Must(template.ParseFiles("templates/server.html", "templates/base.html")) e.Logger.SetLevel(log.DEBUG) e.Pre(middleware.RemoveTrailingSlash()) diff --git a/templates/base.html b/templates/base.html index 834a04b..14ef39c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -86,18 +86,26 @@ <nav class="mt-2"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> <li class="nav-item"> - <a href="#" class="nav-link active"> - <i class="nav-icon fas fa-th"></i> + <a href="/" class="nav-link active"> + <i class="nav-icon fas fa-user-secret"></i> <p> - Dashboard + Wireguard Clients </p> </a> </li> <li class="nav-item"> - <a href="#" class="nav-link"> + <a href="/wg-server" class="nav-link"> <i class="nav-icon fas fa-server"></i> <p> - Server Config + Wireguard Server + </p> + </a> + </li> + <li class="nav-item"> + <a href="/golbal-settings" class="nav-link"> + <i class="nav-icon fas fa-cog"></i> + <p> + Global Settings </p> </a> </li> diff --git a/templates/home.html b/templates/clients.html similarity index 98% rename from templates/home.html rename to templates/clients.html index 9a65140..7347ce2 100644 --- a/templates/home.html +++ b/templates/clients.html @@ -1,5 +1,5 @@ {{define "title"}} -Home +Wireguard Clients {{end}} {{define "username"}} @@ -7,13 +7,13 @@ Home {{end}} {{define "page_title"}} -Dashboard +Wireguard Clients {{end}} {{define "page_content"}} <section class="content"> <div class="container-fluid"> - <h5 class="mt-4 mb-2">Wireguard Clients</h5> + <!-- <h5 class="mt-4 mb-2">Wireguard Clients</h5> --> <div class="row"> {{range .clientDataList}} <div class="col-sm-6"> diff --git a/templates/server.html b/templates/server.html new file mode 100644 index 0000000..3e6c938 --- /dev/null +++ b/templates/server.html @@ -0,0 +1,165 @@ +{{define "title"}} +Wireguard Server +{{end}} + +{{define "username"}} +{{index . "name"}} +{{end}} + +{{define "page_title"}} +Wireguard Server Settings +{{end}} + +{{define "page_content"}} +<section class="content"> + <div class="container-fluid"> + <!-- <h5 class="mt-4 mb-2">Wireguard Server</h5> --> + <div class="row"> + <!-- left column --> + <div class="col-md-6"> + <div class="card card-success"> + <div class="card-header"> + <h3 class="card-title">Interfaces</h3> + </div> + <!-- /.card-header --> + <!-- form start --> + <form role="form" id="frm_server_interface" name="frm_server_interface"> + <div class="card-body"> + <div class="form-group"> + <label for="addresses" class="control-label">Server Interface Addresses</label> + <input type="text" data-role="tagsinput" class="form-control" id="addresses" value=""> + </div> + <div class="form-group"> + <label for="listen_port">Listen Port</label> + <input type="text" class="form-control" id="listen_port" name="listen_port" + placeholder="Listen Port" value="{{ .serverInterface.ListenPort }}"> + </div> + </div> + <!-- /.card-body --> + + <div class="card-footer"> + <button type="submit" class="btn btn-success">Save</button> + </div> + </form> + </div> + <!-- /.card --> + </div> + <!-- right column --> + <div class="col-md-6"> + <div class="card card-danger"> + <div class="card-header"> + <h3 class="card-title">Key Pair</h3> + </div> + <!-- /.card-header --> + <!-- form start --> + <form role="form"> + <div class="card-body"> + <div class="form-group"> + <label for="private_key">Private Key</label> + <div class="input-group input-group"> + <input type="text" class="form-control" id="private_key" placeholder="Private Key"> + <span class="input-group-append"> + <button type="button" class="btn btn-danger btn-flat">Show</button> + </span> + </div> + </div> + <div class="form-group"> + <label for="public_key">Public Key</label> + <input type="text" class="form-control" id="public_key" placeholder="Public Key"> + </div> + </div> + <!-- /.card-body --> + + <div class="card-footer"> + <button type="submit" class="btn btn-danger">Regenerate</button> + </div> + </form> + </div> + <!-- /.card --> + </div> + </div> + <!-- /.row --> + </div> +</section> +{{end}} + +{{define "bottom_js"}} + <script> + function submitServerInterfaceSetting() { + var addresses = $("#addresses").val().split(","); + var listen_port = $("#listen_port").val(); + var data = {"addresses": addresses, "listen_port": listen_port}; + + $.ajax({ + cache: false, + method: 'POST', + url: '/wg-server/interfaces', + dataType: 'json', + contentType: "application/json", + data: JSON.stringify(data), + success: function(data) { + $('#modal_new_client').modal('hide'); + toastr.success('Updated Wireguard server interface addresses successfully'); + }, + error: function(jqXHR, exception) { + var responseJson = jQuery.parseJSON(jqXHR.responseText); + toastr.error(responseJson['message']); + } + }); + } + </script> + <script> + // Wireguard Interface Addresses tag input + $('#addresses').tagsInput({ + 'width': '100%', + // 'height': '75%', + 'interactive': true, + 'defaultText': 'Add More', + 'removeWithBackspace': true, + 'minChars': 0, + 'maxChars': 18, + 'placeholderColor': '#666666' + }); + + // Load server addresses to the form + {{range .serverInterface.Addresses}} + $('#addresses').addTag('{{.}}'); + {{end}} + + // Wireguard Interface Addresses form validation + $(document).ready(function () { + $.validator.setDefaults({ + submitHandler: function () { + submitServerInterfaceSetting(); + } + }); + $('#frm_server_interface').validate({ + rules: { + listen_port: { + required: true, + digits: true, + range: [1, 65535] + } + }, + messages: { + listen_port: { + required: "Please enter a port", + digits: "Port must be an integer", + range: "Port must be in range 1..65535" + } + }, + errorElement: 'span', + errorPlacement: function (error, element) { + error.addClass('invalid-feedback'); + element.closest('.form-group').append(error); + }, + highlight: function (element, errorClass, validClass) { + $(element).addClass('is-invalid'); + }, + unhighlight: function (element, errorClass, validClass) { + $(element).removeClass('is-invalid'); + } + }); + }); + </script> +{{end}} \ No newline at end of file diff --git a/util/util.go b/util/util.go index d825eb0..b9556f0 100644 --- a/util/util.go +++ b/util/util.go @@ -40,7 +40,7 @@ func BuildClientConfig(client model.Client) string { return strConfig } -// ValidateCIDR to validate an network CIDR +// ValidateCIDR to validate a network CIDR func ValidateCIDR(cidr string) bool { _, _, err := net.ParseCIDR(cidr) if err != nil { @@ -49,8 +49,8 @@ func ValidateCIDR(cidr string) bool { return true } -// ValidateAllowedIPs to validate allowed ip addresses in CIDR format. -func ValidateAllowedIPs(cidrs []string) bool { +// ValidateCIDRList to validate a list of network CIDR +func ValidateCIDRList(cidrs []string) bool { for _, cidr := range cidrs { if ValidateCIDR(cidr) == false { return false @@ -58,3 +58,19 @@ func ValidateAllowedIPs(cidrs []string) bool { } return true } + +// ValidateAllowedIPs to validate allowed ip addresses in CIDR format +func ValidateAllowedIPs(cidrs []string) bool { + if ValidateCIDRList(cidrs) == false { + return false + } + return true +} + +// ValidateServerAddresses to validate allowed ip addresses in CIDR format +func ValidateServerAddresses(cidrs []string) bool { + if ValidateCIDRList(cidrs) == false { + return false + } + return true +}