diff --git a/README.md b/README.md index f39d13c..affeda3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,27 @@ A manager and API to add, remove clients as well as other features such as an au This GoLang application runs an API which can be made **https** ready using a LetsEncrypt certificate. The program creates directories in the directory ``/opt/wgManagerAPI`` (This needs to be created manually before hand). In the ``/opt/wgManagerAPI`` directory we have a few more sub-directories such as ``/logs`` which contain logs of the application and ``/wg`` which contains our SQLite database. The SQLite database contains tables which store information such as generated and available IPs, client configuration (public key and preshared key) as well as the Wireguard server own private key, public key, IP Addresses and ListenPort. -# How to use + +## Content + +**[1. .env File](#env-file)** + +**[2. Deployment](#deployment)** + - [2.1. Docker](#docker) + - [2.2. Docker-compose](#docker-compose) + - [2.3. Building from source](#building-from-source) + - [2.3.1. Code](#code) + - [2.3.2. Dockerfile](#dockerfile) + +**[3. Communicating with the API](#communicating-with-the-api)** + - [3.1 Adding keys](#adding-keys) + - [3.2 Removing keys](#removing-keys) + - [3.3 Enabling keys](#enabling-keys) + - [3.4 Disabling keys](#disabling-keys) + +**[4. Debugging](#debugging)** + - [4.1 Logs](#logs) + - [4.2 FAQ](#faq) ## .env File A .env file needs to be placed in the directory `/opt/wgManagerAPI/.env` containing the following: @@ -72,9 +92,10 @@ services: The docker-compose file is the easiest way to get software up and running. Do not forget to add your ``.env`` file to ``/opt/wgManagerAPI/.env`` ### Building from source -Building from source allows you to create an executable file which can be created into a Systemd service or equivalent. Running the executable must be run with sudo (recommended) or root (not recommended). +#### Code +Building from source allows you to create an executable file which can be created into a Systemd service or equivalent. It also allows you to build for a different architecture such as ARM64. Running the executable must be run with sudo (recommended) or root (not recommended). -Do not forget to add your ``.env`` file to ``/opt/wgManagerAPI/.env`` +Do not forget to add your `.env` file to `/opt/wgManagerAPI/.env` 1. Install Go 1.14+ on to your machine 2. git clone this repository 3. ``cd wireguard-manager-and-api`` to open the repo @@ -82,15 +103,23 @@ Do not forget to add your ``.env`` file to ``/opt/wgManagerAPI/.env`` 5. ``go build -o wgManagerAPI main.go`` to build an output a executable file 6. ``sudo ./wgManagerAPI`` to run the application. -## Communicating with the API -### Adding keys +#### Dockerfile +Building a docker image from scratch enables you to create an image specific to a specific architecture such as ARM64 as prebuilt images in our docker image repository is made for AMD64 architecture. -URL: `POST` request to `http(s)://domain.com:PORT/manager/keys` +Do not forget to add your `.env`` file to ``/opt/wgManagerAPI/.env` +1. Install docker +2. Clone the git repository and open the directory +3. ``sudo docker build -t wireguard-manager-and-api:YOURTAGHERE .`` to build the docker image locally + +## Communicating with the API -Header: `Content-Type: application/json` +With almost any API error the server will give back a ``400 Bad Request`` status code. Please read the JSON response file "response" to get the error information. -Header (If authentication is enabled): `authorization:(AUTH key from .env)` +### Adding keys +URL: `POST` request to `http(s)://domain.com:PORT/manager/keys` +Header: `Content-Type: application/json` +Header (If authentication is enabled): `authorization:(AUTH key from .env)` Body: ```json { @@ -98,7 +127,8 @@ Body: "presharedKey": "(Wireguard client preshared key)" } ``` -Response: +Response: +Status Code `202` ```json { "allowedIPs": "0.0.0.0/0, ::/0", @@ -112,7 +142,7 @@ Response: "response": "Added key successfully" } ``` -Parsing response into client config: +Parsing response into Wireguard client config: ``` [Interface] PrivateKey = (Wireguard client private key) @@ -127,32 +157,72 @@ Endpoint = (public IP of server):51820 ``` ### Removing keys -URL: `DELETE` request to `http(s)://domain.com:PORT/manager/keys` - -Header: `Content-Type: application/json` - -Header (If authentication is enabled): `authorization:(AUTH key from .env)` - +URL: `DELETE` request to `http(s)://domain.com:PORT/manager/keys` +Header: `Content-Type: application/json` +Header (If authentication is enabled): `authorization:(AUTH key from .env)` Body: ```json { "keyID": "(Database keyID)" } ``` -Response: +Response: +Status Code `202` ```json { "response": "Key deleted" } ``` +### Enabling keys +URL: `POST` request to `http(s)://domain.com:PORT/manager/keys/enable` +Header: `Content-Type: application/json` +Header (If authentication is enabled): `authorization:(AUTH key from .env)` +Body: +```json +{ + "keyID": "(Database keyID)" +} +``` +Response: +Status Code `202` +```json +{ + "response": "Enabled key successfully" +} +``` + +### Disabling keys +URL: `POST` request to `http(s)://domain.com:PORT/manager/keys/disable` +Header: `Content-Type: application/json` +Header (If authentication is enabled): `authorization:(AUTH key from .env)` +Body: +```json +{ + "keyID": "(Database keyID)" +} +``` +Response: +Status Code `202` +```json +{ + "response": "Disabled key successfully" +} +``` ## Debugging ### Logs If the Wireguard Manager and API application fails to start you should always look at your logs and the errors to see the problems look at ``/opt/wgManagerAPI/logs/`` folder and open the latest log using ``nano`` or any other text editor. ### FAQ -Haha nothing here +**Q:** The prebuilt source file or docker image is not working properly. +**A:** Build from dockerfile or code from source. The prebuilt docker images are not for ARM architecture. + +**Q:** IPv6 is not working with the docker image. +**A:** We have not been able to setup IPv6 on the docker-compose file successfully. If you find a solution please tell us. + +**Q:** I have built from source code but unable to successfully route clients through the VPN +**A:** You may need the iptables rule: ``sudo iptables -t nat -A POSTROUTING -o (INTERFACE I.E eth0 or enp0s3) -j MASQUERADE``. This will be required on boot everytime. We will try and implement this into the program in the future. diff --git a/src/api/api.go b/src/api/api.go deleted file mode 100644 index 35bd0d6..0000000 --- a/src/api/api.go +++ /dev/null @@ -1,148 +0,0 @@ -package api - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "net/http" - "os" - - "github.com/gorilla/mux" - "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/db" -) - -type keyAdd struct { - PublicKey string `json:"publicKey"` - PresharedKey string `json:"presharedKey"` - KeyID string `json:"keyID"` -} -type keyDelete struct { - KeyID string `json:"keyID"` -} - -type authStruct struct { - Active bool `json:"active"` -} - -func API() { - router := mux.NewRouter() //Router for routes - router.HandleFunc("/manager/keys", keyCreate).Methods("POST") //post route for adding keys - router.HandleFunc("/manager/keys", keyRemove).Methods("DELETE") //delete route for removing keys - - serverDev := os.Getenv("SERVER_SECURITY") - if serverDev == "disabled" { - port := os.Getenv("PORT") - fmt.Printf("Info - HTTP about to listen on %s.", port) - log.Printf("Info - HTTP about to listen on %s.", port) - - err := http.ListenAndServe(":"+port, router) - log.Fatal("Error - Startup of API server", err) - } else { - port := os.Getenv("PORT") - fullchainCert := os.Getenv("FULLCHAIN_CERT") - privKeyCert := os.Getenv("PK_CERT") - - log.Printf("HTTPS about to listen on %s.", port) - fmt.Printf("HTTPS about to listen on %s.", port) - - err := http.ListenAndServeTLS(":"+port, - fullchainCert, //fullchain - privKeyCert, router) - log.Fatal("Error - Startup of API server", err) - } - -} - -func parseResponse(req *http.Request, schema interface{}) error { - var unmarshalErr *json.UnmarshalTypeError - - headerContentTtype := req.Header.Get("Content-Type") - if headerContentTtype != "application/json" { - return errors.New("content type is not application/json") - } - - decoder := json.NewDecoder(req.Body) - decoder.DisallowUnknownFields() //throws error if uneeded JSON is added - err := decoder.Decode(schema) //decodes the incoming JSON into the struct - if err != nil { - if errors.As(err, &unmarshalErr) { - return errors.New("Bad Request. Wrong Type provided for field " + unmarshalErr.Field) - } else { - return errors.New("Bad Request " + err.Error()) - } - } - return nil -} - -func keyCreate(res http.ResponseWriter, req *http.Request) { - var incomingJson keyAdd - - err := parseResponse(req, &incomingJson) //parse JSON - if err != nil { - log.Println("Error - Parsing request", err) - response(res, map[string]string{"response": err.Error()}, http.StatusBadRequest) - return - } - - if incomingJson.PresharedKey == "" || incomingJson.PublicKey == "" { - response(res, map[string]string{"response": "Bad Request, presharedKey and publicKey must be filled"}, http.StatusBadRequest) - return - } - if os.Getenv("AUTH") != "-" { //check AUTH - authHeader := req.Header.Get("Authorization") - if os.Getenv("AUTH") != authHeader { - response(res, map[string]string{"response": "Authentication key is not valid"}, http.StatusBadRequest) - return - } - } - - boolRes, mapRes := db.CreateKey(incomingJson.PublicKey, incomingJson.PresharedKey) //add key to db - if !boolRes { - response(res, mapRes, http.StatusBadRequest) - } else { - response(res, mapRes, http.StatusAccepted) - } -} - -func keyRemove(res http.ResponseWriter, req *http.Request) { - var incomingJson keyDelete - - jsonResponse := make(map[string]string) - - err := parseResponse(req, &incomingJson) - if err != nil { - log.Println(err) - response(res, map[string]string{"response": err.Error()}, http.StatusBadRequest) - return - } - - if incomingJson.KeyID == "" { - jsonResponse["response"] = "Bad Request, keyID needs to be filled" - response(res, map[string]string{"response": "Bad Request, keyID needs to be filled"}, http.StatusBadRequest) - return - } - - if os.Getenv("AUTH") != "-" { //check AUTH - authHeader := req.Header.Get("Authorization") - if os.Getenv("AUTH") != authHeader { - response(res, map[string]string{"response": "Authentication key is not valid"}, http.StatusBadRequest) - return - } - } - - boolRes, mapRes := db.DeleteKey(incomingJson.KeyID) - if !boolRes { - response(res, mapRes, http.StatusBadRequest) - } else { - //need to add in pubKey of server, dns, allowedIP - response(res, mapRes, http.StatusAccepted) - } -} - -func response(res http.ResponseWriter, responseMap map[string]string, httpStatusCode int) { - res.Header().Set("Content-Type", "application/json") - res.WriteHeader(httpStatusCode) - jsonResp, _ := json.Marshal(responseMap) - res.Write(jsonResp) -} diff --git a/src/api/apiServer.go b/src/api/apiServer.go new file mode 100644 index 0000000..8eec281 --- /dev/null +++ b/src/api/apiServer.go @@ -0,0 +1,45 @@ +package api + +import ( + "fmt" + "log" + "net" + "net/http" + "os" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/api/router" +) + +type authStruct struct { + Active bool `json:"active"` +} + +func API() { + newRouter := router.NewRouter() + + serverDev := os.Getenv("SERVER_SECURITY") + if serverDev == "disabled" { + port := os.Getenv("PORT") + fmt.Printf("Info - HTTP about to listen on %s.", port) + log.Printf("Info - HTTP about to listen on %s.", port) + + resolve, _ := net.ResolveTCPAddr("tcp4", "0.0.0.0:8443") + resolveTCP, _ := net.ListenTCP("tcp4", resolve) + + errServer := http.Serve(resolveTCP, newRouter) + log.Fatal("Error - Startup of API server", errServer) + } else { + port := os.Getenv("PORT") + fullchainCert := os.Getenv("FULLCHAIN_CERT") + privKeyCert := os.Getenv("PK_CERT") + + log.Printf("HTTPS about to listen on %s.", port) + fmt.Printf("HTTPS about to listen on %s.", port) + + resolve, _ := net.ResolveTCPAddr("tcp4", "0.0.0.0:8443") + resolveTCP, _ := net.ListenTCP("tcp4", resolve) + + errServer := http.ServeTLS(resolveTCP, newRouter, fullchainCert, privKeyCert) + log.Fatal("Error - Startup of API server", errServer) + } +} diff --git a/src/api/router/keyCreate.go b/src/api/router/keyCreate.go new file mode 100644 index 0000000..a51d9a2 --- /dev/null +++ b/src/api/router/keyCreate.go @@ -0,0 +1,45 @@ +package router + +import ( + "log" + "net/http" + "os" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/db" +) + +type keyCreateJSON struct { + PublicKey string `json:"publicKey"` + PresharedKey string `json:"presharedKey"` + KeyID string `json:"keyID"` +} + +func keyCreate(res http.ResponseWriter, req *http.Request) { + var incomingJson keyCreateJSON + + err := parseResponse(req, &incomingJson) //parse JSON + if err != nil { + log.Println("Error - Parsing request", err) + sendResponse(res, map[string]string{"response": err.Error()}, http.StatusBadRequest) + return + } + + if incomingJson.PresharedKey == "" || incomingJson.PublicKey == "" { + sendResponse(res, map[string]string{"response": "Bad Request, presharedKey and publicKey must be filled"}, http.StatusBadRequest) + return + } + if os.Getenv("AUTH") != "-" { //check AUTH + authHeader := req.Header.Get("Authorization") + if os.Getenv("AUTH") != authHeader { + sendResponse(res, map[string]string{"response": "Authentication key is not valid"}, http.StatusBadRequest) + return + } + } + + boolRes, mapRes := db.CreateKey(incomingJson.PublicKey, incomingJson.PresharedKey) //add key to db + if !boolRes { + sendResponse(res, mapRes, http.StatusBadRequest) + } else { + sendResponse(res, mapRes, http.StatusAccepted) + } +} diff --git a/src/api/router/keyDisable.go b/src/api/router/keyDisable.go new file mode 100644 index 0000000..e874301 --- /dev/null +++ b/src/api/router/keyDisable.go @@ -0,0 +1,49 @@ +package router + +import ( + "log" + "net/http" + "os" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/db" +) + +type keyDisableJSON struct { + KeyID string `json:"keyID"` +} + +func keyDisable(res http.ResponseWriter, req *http.Request) { + var incomingJson keyDisableJSON + + jsonResponse := make(map[string]string) + + err := parseResponse(req, &incomingJson) + if err != nil { + log.Println(err) + sendResponse(res, map[string]string{"response": err.Error()}, http.StatusBadRequest) + return + } + + if os.Getenv("AUTH") != "-" { //check AUTH + authHeader := req.Header.Get("Authorization") + if os.Getenv("AUTH") != authHeader { + sendResponse(res, map[string]string{"response": "Authentication key is not valid"}, http.StatusBadRequest) + return + } + } + + if incomingJson.KeyID == "" { + jsonResponse["response"] = "Bad Request, keyID needs to be filled" + sendResponse(res, map[string]string{"response": "Bad Request, keyID needs to be filled"}, http.StatusBadRequest) + return + } + + keyID := incomingJson.KeyID + + boolRes, mapRes := db.DisableKey(keyID) + if !boolRes { + sendResponse(res, mapRes, http.StatusBadRequest) + } else { + sendResponse(res, mapRes, http.StatusAccepted) + } +} diff --git a/src/api/router/keyEnable.go b/src/api/router/keyEnable.go new file mode 100644 index 0000000..6654c59 --- /dev/null +++ b/src/api/router/keyEnable.go @@ -0,0 +1,49 @@ +package router + +import ( + "log" + "net/http" + "os" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/db" +) + +type keyEnableJSON struct { + KeyID string `json:"keyID"` +} + +func keyEnable(res http.ResponseWriter, req *http.Request) { + var incomingJson keyEnableJSON + + jsonResponse := make(map[string]string) + + err := parseResponse(req, &incomingJson) + if err != nil { + log.Println(err) + sendResponse(res, map[string]string{"response": err.Error()}, http.StatusBadRequest) + return + } + + if os.Getenv("AUTH") != "-" { //check AUTH + authHeader := req.Header.Get("Authorization") + if os.Getenv("AUTH") != authHeader { + sendResponse(res, map[string]string{"response": "Authentication key is not valid"}, http.StatusBadRequest) + return + } + } + + if incomingJson.KeyID == "" { + jsonResponse["response"] = "Bad Request, keyID needs to be filled" + sendResponse(res, map[string]string{"response": "Bad Request, keyID needs to be filled"}, http.StatusBadRequest) + return + } + + keyID := incomingJson.KeyID + + boolRes, mapRes := db.EnableKey(keyID) + if !boolRes { + sendResponse(res, mapRes, http.StatusBadRequest) + } else { + sendResponse(res, mapRes, http.StatusAccepted) + } +} diff --git a/src/api/router/keyRemove.go b/src/api/router/keyRemove.go new file mode 100644 index 0000000..cdd585e --- /dev/null +++ b/src/api/router/keyRemove.go @@ -0,0 +1,48 @@ +package router + +import ( + "log" + "net/http" + "os" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/db" +) + +type keyRemoveJSON struct { + KeyID string `json:"keyID"` +} + +func keyRemove(res http.ResponseWriter, req *http.Request) { + var incomingJson keyRemoveJSON + + jsonResponse := make(map[string]string) + + err := parseResponse(req, &incomingJson) + if err != nil { + log.Println(err) + sendResponse(res, map[string]string{"response": err.Error()}, http.StatusBadRequest) + return + } + + if incomingJson.KeyID == "" { + jsonResponse["response"] = "Bad Request, keyID needs to be filled" + sendResponse(res, map[string]string{"response": "Bad Request, keyID needs to be filled"}, http.StatusBadRequest) + return + } + + if os.Getenv("AUTH") != "-" { //check AUTH + authHeader := req.Header.Get("Authorization") + if os.Getenv("AUTH") != authHeader { + sendResponse(res, map[string]string{"response": "Authentication key is not valid"}, http.StatusBadRequest) + return + } + } + + boolRes, mapRes := db.DeleteKey(incomingJson.KeyID) + if !boolRes { + sendResponse(res, mapRes, http.StatusBadRequest) + } else { + //need to add in pubKey of server, dns, allowedIP + sendResponse(res, mapRes, http.StatusAccepted) + } +} diff --git a/src/api/router/parseResponse.go b/src/api/router/parseResponse.go new file mode 100644 index 0000000..072853b --- /dev/null +++ b/src/api/router/parseResponse.go @@ -0,0 +1,28 @@ +package router + +import ( + "encoding/json" + "errors" + "net/http" +) + +func parseResponse(req *http.Request, schema interface{}) error { + var unmarshalErr *json.UnmarshalTypeError + + headerContentTtype := req.Header.Get("Content-Type") + if headerContentTtype != "application/json" { + return errors.New("content type is not application/json") + } + + decoder := json.NewDecoder(req.Body) + decoder.DisallowUnknownFields() //throws error if uneeded JSON is added + err := decoder.Decode(schema) //decodes the incoming JSON into the struct + if err != nil { + if errors.As(err, &unmarshalErr) { + return errors.New("Bad Request. Wrong Type provided for field " + unmarshalErr.Field) + } else { + return errors.New("Bad Request " + err.Error()) + } + } + return nil +} diff --git a/src/api/router/router.go b/src/api/router/router.go new file mode 100644 index 0000000..db445d0 --- /dev/null +++ b/src/api/router/router.go @@ -0,0 +1,17 @@ +package router + +import "github.com/gorilla/mux" + +func NewRouter() *mux.Router { + router := mux.NewRouter() //Router for routes + + manager := router.PathPrefix("/manager").Subrouter() //main subrouter + + keys := manager.PathPrefix("/keys").Subrouter() //specific subrouter + + keys.HandleFunc("", keyCreate).Methods("POST") //post route for adding keys + keys.HandleFunc("", keyRemove).Methods("DELETE") //delete route for removing keys + keys.HandleFunc("/enable", keyEnable).Methods("POST") //post route for enabling key + keys.HandleFunc("/disable", keyDisable).Methods("POST") //post route for disabling key + return router +} diff --git a/src/api/router/sendResponse.go b/src/api/router/sendResponse.go new file mode 100644 index 0000000..f7db600 --- /dev/null +++ b/src/api/router/sendResponse.go @@ -0,0 +1,13 @@ +package router + +import ( + "encoding/json" + "net/http" +) + +func sendResponse(res http.ResponseWriter, responseMap map[string]string, httpStatusCode int) { + res.Header().Set("Content-Type", "application/json") + res.WriteHeader(httpStatusCode) + jsonResp, _ := json.Marshal(responseMap) + res.Write(jsonResp) +} diff --git a/src/db/createKey.go b/src/db/createKey.go new file mode 100644 index 0000000..d0caa47 --- /dev/null +++ b/src/db/createKey.go @@ -0,0 +1,61 @@ +package db + +import ( + "errors" + "log" + "os" + "strconv" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/manager" + "gorm.io/gorm" +) + +func CreateKey(pubKey string, preKey string) (bool, map[string]string) { + var ipStruct IP + var wgStruct WireguardInterface + responseMap := make(map[string]string) + db := DBSystem + + resultIP := db.Where("in_use = ?", "false").First(&ipStruct) //find IP not in use + if errors.Is(resultIP.Error, gorm.ErrRecordNotFound) { + responseMap["response"] = "No available IPs on the server" + return false, responseMap + } + + keyStructCreate := Key{PublicKey: pubKey, PresharedKey: preKey, IPv4Address: ipStruct.IPv4Address, Enabled: "true"} //create Key object + resultKeyCreate := db.Create(&keyStructCreate) //add object to db + if resultKeyCreate.Error != nil { + log.Println("Error - Adding key to db", resultKeyCreate.Error) + responseMap["response"] = "Error when adding key to database" + return false, responseMap + } + ipStruct.InUse = "true" //set ip to in use + db.Save(&ipStruct) //update IP in db + keyIDStr := strconv.Itoa(keyStructCreate.KeyID) //convert keyID to string + boolRes, strRes := manager.AddKey(ipStruct.WGInterface, ipStruct.IPv4Address, ipStruct.IPv6Address, pubKey, preKey) //add key to wg interface + if !boolRes { //if an error occurred + responseMap["response"] = strRes + return boolRes, responseMap + } else { + responseMap["response"] = "Added key successfully" + responseMap["ipv4Address"] = ipStruct.IPv4Address + "/32" + if ipStruct.IPv6Address != "-" { + responseMap["ipv6Address"] = ipStruct.IPv6Address + "/128" + } + responseMap["ipAddress"] = os.Getenv("IP_ADDRESS") + responseMap["dns"] = os.Getenv("DNS") + responseMap["allowedIPs"] = os.Getenv("ALLOWED_IP") + responseMap["keyID"] = keyIDStr + } + + resultWG := db.Where("interface_name = ?", "wg0").First(&wgStruct) //get wireguard server info + + if resultWG.Error != nil { + responseMap["response"] = "Issue in finding a key for the server" + return false, responseMap + } else { + responseMap["publicKey"] = wgStruct.PublicKey //return back wg server pub key + responseMap["listenPort"] = strconv.Itoa(wgStruct.ListenPort) //return back wg server listenPort + return boolRes, responseMap + } +} diff --git a/src/db/createWG.go b/src/db/createWG.go new file mode 100644 index 0000000..e423ded --- /dev/null +++ b/src/db/createWG.go @@ -0,0 +1,34 @@ +package db + +import "log" + +func createWG(PrivateKey string, PublicKey string, ListenPort int, IPv4Address string, IPv6Address string) { + log.Println("Info - Creating wireguard interface") + db := DBSystem + var wgCreate WireguardInterface + + if IPv6Address != "-" { + wgCreate = WireguardInterface{ //with IPv6 + InterfaceName: "wg0", + PrivateKey: PrivateKey, + PublicKey: PublicKey, + ListenPort: ListenPort, + IPv4Address: IPv4Address, + IPv6Address: IPv6Address, + } + } else { + wgCreate = WireguardInterface{ //without IPv6 + InterfaceName: "wg0", + PrivateKey: PrivateKey, + PublicKey: PublicKey, + ListenPort: ListenPort, + IPv4Address: IPv4Address, + } + } + + err := db.Create(wgCreate).Error + if err != nil { + log.Fatal("Creating interface", err) + } + log.Println("Successfully created interface") +} diff --git a/src/db/db.go b/src/db/db.go index 03b5278..3ec0e84 100644 --- a/src/db/db.go +++ b/src/db/db.go @@ -4,39 +4,14 @@ import ( "errors" "log" "math" - "net" "os" "strconv" "strings" - "time" - "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/manager" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "gorm.io/driver/sqlite" "gorm.io/gorm" ) -type Key struct { - KeyID int `gorm:"primaryKey;autoIncrement"` - PublicKey string `gorm:"unique"` - PresharedKey string `gorm:"unique"` - IPv4Address string `gorm:"foreignKey:IPv4Address"` -} -type IP struct { - IPv4Address string `gorm:"primaryKey"` - IPv6Address string `gorm:"unique"` - InUse string - WGInterface string -} -type WireguardInterface struct { - InterfaceName string `gorm:"primaryKey"` - PrivateKey string `gorm:"unique"` - PublicKey string `gorm:"unique"` - ListenPort int `gorm:"unique"` - IPv4Address string - IPv6Address string -} - var DBSystem *gorm.DB func DBStart() { @@ -51,8 +26,8 @@ func DBStart() { } DBSystem = db //set global variable up - // Migrate the schema + // Migrate the schema errMigrate := db.AutoMigrate(&Key{}, &IP{}, &WireguardInterface{}) //Migrate tables to sqlite if errMigrate != nil { log.Fatal("Error - Migrating database", errMigrate) @@ -62,159 +37,6 @@ func DBStart() { generateIPs() } -func WGStart() { - log.Println("Info - Starting up wg interface") - - var wgInterface WireguardInterface - db := DBSystem - result := db.Where("interface_name = ?", "wg0").First(&wgInterface) //find nterface in sqlite db - - if result.Error != nil { //if an interface is not found, create one - pkServer, errPk := wgtypes.GeneratePrivateKey() - if errPk != nil { - log.Fatal("Error - Generating new private key", errPk) - } - - pubServer := pkServer.PublicKey() - - ipv4Addr := os.Getenv("WG_IPV4") - ipv6Addr := os.Getenv("WG_IPV6") - - if ipv6Addr != "-" { - createWG(pkServer.String(), pubServer.String(), 51820, ipv4Addr+"/16", ipv6Addr+"/64") - } else { - createWG(pkServer.String(), pubServer.String(), 51820, ipv4Addr+"/16", "-") - } - - peers := generatePeerArray() - manager.AddPeersInterface("wg0", pkServer.String(), 51820, peers) - return - } - - peers := generatePeerArray() - manager.AddPeersInterface("wg0", wgInterface.PrivateKey, wgInterface.ListenPort, peers) -} - -func createWG(PrivateKey string, PublicKey string, ListenPort int, IPv4Address string, IPv6Address string) { - log.Println("Info - Creating wireguard interface") - db := DBSystem - var wgCreate WireguardInterface - - if IPv6Address != "-" { - wgCreate = WireguardInterface{ //with IPv6 - InterfaceName: "wg0", - PrivateKey: PrivateKey, - PublicKey: PublicKey, - ListenPort: ListenPort, - IPv4Address: IPv4Address, - IPv6Address: IPv6Address, - } - } else { - wgCreate = WireguardInterface{ //without IPv6 - InterfaceName: "wg0", - PrivateKey: PrivateKey, - PublicKey: PublicKey, - ListenPort: ListenPort, - IPv4Address: IPv4Address, - } - } - - err := db.Create(wgCreate).Error - if err != nil { - log.Fatal("Creating interface", err) - } - log.Println("Successfully created interface") -} - -func CreateKey(pubKey string, preKey string) (bool, map[string]string) { - var ipStruct IP - var wgStruct WireguardInterface - responseMap := make(map[string]string) - db := DBSystem - - resultIP := db.Where("in_use = ?", "false").First(&ipStruct) //find IP not in use - if errors.Is(resultIP.Error, gorm.ErrRecordNotFound) { - responseMap["response"] = "No available IPs on the server" - return false, responseMap - } - - keyStructCreate := Key{PublicKey: pubKey, PresharedKey: preKey, IPv4Address: ipStruct.IPv4Address} //create Key object - resultKeyCreate := db.Create(&keyStructCreate) //add object to db - if resultKeyCreate.Error != nil { - log.Println("Error - Adding key to db", resultKeyCreate.Error) - responseMap["response"] = "Error when adding key to database" - return false, responseMap - } - ipStruct.InUse = "true" //set ip to in use - db.Save(&ipStruct) //update IP in db - keyIDStr := strconv.Itoa(keyStructCreate.KeyID) //convert keyID to string - boolRes, strRes := manager.AddKey(ipStruct.WGInterface, ipStruct.IPv4Address, ipStruct.IPv6Address, pubKey, preKey) //add key to wg interface - if !boolRes { //if an error occurred - responseMap["response"] = strRes - return boolRes, responseMap - } else { - responseMap["response"] = "Added key successfully" - responseMap["ipv4Address"] = ipStruct.IPv4Address + "/32" - if ipStruct.IPv6Address != "-" { - responseMap["ipv6Address"] = ipStruct.IPv6Address + "/128" - } - responseMap["ipAddress"] = os.Getenv("IP_ADDRESS") - responseMap["dns"] = os.Getenv("DNS") - responseMap["allowedIPs"] = os.Getenv("ALLOWED_IP") - responseMap["keyID"] = keyIDStr - } - - resultWG := db.Where("interface_name = ?", "wg0").First(&wgStruct) //get wireguard server info - - if resultWG.Error != nil { - responseMap["response"] = "Issue in finding a key for the server" - return false, responseMap - } else { - responseMap["publicKey"] = wgStruct.PublicKey //return back wg server pub key - responseMap["listenPort"] = strconv.Itoa(wgStruct.ListenPort) //return back wg server listenPort - return boolRes, responseMap - } -} - -func DeleteKey(keyID string) (bool, map[string]string) { - var ipStruct IP - var keyStruct Key - responseMap := make(map[string]string) - db := DBSystem - - keyIDInt, _ := strconv.Atoi(keyID) //convert key string to int - resultKey := db.Where("key_id = ?", keyIDInt).First(&keyStruct) //find key in database - if errors.Is(resultKey.Error, gorm.ErrRecordNotFound) { - responseMap["response"] = "Key was not found on the server" - return false, responseMap - } - - pubKey := keyStruct.PublicKey //set pub key - ipv4 := keyStruct.IPv4Address //set ipv4 address - resultDel := db.Where("key_id = ?", keyID).Delete(&keyStruct) //delete key from db - if resultDel.Error != nil { - log.Println("Finding key in DB", resultDel.Error) - responseMap["response"] = "Error occurred when finding the key in database" - return false, responseMap - } - - resultIP := db.Where("ipv4_address = ?", ipv4).First(&ipStruct) //find IP in db - if errors.Is(resultIP.Error, gorm.ErrRecordNotFound) { - responseMap["response"] = "Key was not found on the server" - return false, responseMap - } - - ipStruct.InUse = "false" //set IP back to unused - ipUpdate := db.Save(&ipStruct) //save data - if ipUpdate.Error != nil { - responseMap["response"] = "Error in updating IP" - return false, responseMap - } - boolRes, stringRes := manager.DeleteKey("wg0", pubKey) //delete key from wg interface - responseMap["response"] = stringRes - return boolRes, responseMap -} - func generateIPs() { log.Println("Info - Generating IPs") @@ -291,57 +113,3 @@ func generateIPs() { } } } -func generatePeerArray() []wgtypes.PeerConfig { - var keyStruct []Key //key struct - var keyArray []wgtypes.PeerConfig //peers (clients) - db := DBSystem - - resultKey := db.Find(&keyStruct) - if errors.Is(resultKey.Error, gorm.ErrRecordNotFound) { - return keyArray - } else if resultKey.Error != nil { - log.Println("Error - Finding keys", resultKey.Error) - } - - for i := 0; i < len(keyStruct); i++ { //loop over all clients in db - var ipStruct IP - resultIP := db.Where("ipv4_address = ?", keyStruct[i].IPv4Address).First(&ipStruct) - if errors.Is(resultIP.Error, gorm.ErrRecordNotFound) { - log.Println("Cant find IPs", keyStruct[i].IPv4Address) - continue //continue even on error - } else if resultIP.Error != nil { - log.Println("Error - Finding IPs", keyStruct[i].IPv4Address, resultKey.Error) - } - - pubKey, pubErr := manager.ParseKey(keyStruct[i].PublicKey) - preKey, preErr := manager.ParseKey(keyStruct[i].PresharedKey) - if pubErr != nil || preErr != nil { - log.Fatal("Error - Unable to parse keys on generate array") - } - - var ipAddresses []net.IPNet - ipv4, errIPv4 := manager.ParseIP(ipStruct.IPv4Address + "/32") - if errIPv4 != nil { - log.Fatal("Error - Parsing IPv4 Address", errIPv4) - } - ipAddresses = append(ipAddresses, *ipv4) - - if ipStruct.IPv6Address != "-" { - ipv6, errIPv6 := manager.ParseIP(ipStruct.IPv6Address + "/128") - if errIPv6 != nil { - log.Fatal("Error - Parsing IPv6 Address", errIPv6) - } - ipAddresses = append(ipAddresses, *ipv6) - } - - var zeroTime time.Duration - userConfig := wgtypes.PeerConfig{ - PublicKey: pubKey, - PresharedKey: &preKey, - PersistentKeepaliveInterval: &zeroTime, - AllowedIPs: ipAddresses, - } - keyArray = append(keyArray, userConfig) //add config to client array - } - return keyArray -} diff --git a/src/db/deleteKey.go b/src/db/deleteKey.go new file mode 100644 index 0000000..6849fdc --- /dev/null +++ b/src/db/deleteKey.go @@ -0,0 +1,49 @@ +package db + +import ( + "errors" + "log" + "strconv" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/manager" + "gorm.io/gorm" +) + +func DeleteKey(keyID string) (bool, map[string]string) { + var ipStruct IP + var keyStruct Key + responseMap := make(map[string]string) + db := DBSystem + + keyIDInt, _ := strconv.Atoi(keyID) //convert key string to int + resultKey := db.Where("key_id = ?", keyIDInt).First(&keyStruct) //find key in database + if errors.Is(resultKey.Error, gorm.ErrRecordNotFound) { + responseMap["response"] = "Key was not found on the server" + return false, responseMap + } + + pubKey := keyStruct.PublicKey //set pub key + ipv4 := keyStruct.IPv4Address //set ipv4 address + resultDel := db.Where("key_id = ?", keyID).Delete(&keyStruct) //delete key from db + if resultDel.Error != nil { + log.Println("Finding key in DB", resultDel.Error) + responseMap["response"] = "Error occurred when finding the key in database" + return false, responseMap + } + + resultIP := db.Where("ipv4_address = ?", ipv4).First(&ipStruct) //find IP in db + if errors.Is(resultIP.Error, gorm.ErrRecordNotFound) { + responseMap["response"] = "Key was not found on the server" + return false, responseMap + } + + ipStruct.InUse = "false" //set IP back to unused + ipUpdate := db.Save(&ipStruct) //save data + if ipUpdate.Error != nil { + responseMap["response"] = "Error in updating IP" + return false, responseMap + } + boolRes, stringRes := manager.DeleteKey("wg0", pubKey) //delete key from wg interface + responseMap["response"] = stringRes + return boolRes, responseMap +} diff --git a/src/db/disableKey.go b/src/db/disableKey.go new file mode 100644 index 0000000..327af1c --- /dev/null +++ b/src/db/disableKey.go @@ -0,0 +1,37 @@ +package db + +import ( + "errors" + "strconv" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/manager" + "gorm.io/gorm" +) + +func DisableKey(keyID string) (bool, map[string]string) { + var keyStruct Key + responseMap := make(map[string]string) + db := DBSystem + + keyIDInt, _ := strconv.Atoi(keyID) //convert key string to int + resultKey := db.Where("key_id = ?", keyIDInt).First(&keyStruct) //find key in database + if errors.Is(resultKey.Error, gorm.ErrRecordNotFound) { + responseMap["response"] = "Key was not found on the server" + return false, responseMap + } + + keyStruct.Enabled = "false" //set IP back to unused + keyUpdate := db.Save(&keyStruct) //save data + if keyUpdate.Error != nil { + responseMap["response"] = "Error in updating client key" + return false, responseMap + } + pubKey := keyStruct.PublicKey + boolRes, stringRes := manager.DeleteKey("wg0", pubKey) //delete key from wg interface + if boolRes { + responseMap["response"] = "Disabled key successfully" + } else { + responseMap["response"] = stringRes + } + return boolRes, responseMap +} diff --git a/src/db/enableKey.go b/src/db/enableKey.go new file mode 100644 index 0000000..0f0f2cd --- /dev/null +++ b/src/db/enableKey.go @@ -0,0 +1,48 @@ +package db + +import ( + "errors" + "strconv" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/manager" + "gorm.io/gorm" +) + +func EnableKey(keyID string) (bool, map[string]string) { + var keyStruct Key + var ipStruct IP + responseMap := make(map[string]string) + db := DBSystem + + keyIDInt, _ := strconv.Atoi(keyID) //convert key string to int + resultKey := db.Where("key_id = ?", keyIDInt).First(&keyStruct) //find key in database + if errors.Is(resultKey.Error, gorm.ErrRecordNotFound) { + responseMap["response"] = "Key was not found on the server" + return false, responseMap + } + + keyStruct.Enabled = "true" //set IP back to unused + keyUpdate := db.Save(&keyStruct) //save data + if keyUpdate.Error != nil { + responseMap["response"] = "Error in updating client key" + return false, responseMap + } + + ipv4Addr := keyStruct.IPv4Address + pubKey := keyStruct.PublicKey + preKey := keyStruct.PresharedKey + + resultIP := db.Where("ipv4_address = ?", ipv4Addr).First(&ipStruct) //find IP in db + if errors.Is(resultIP.Error, gorm.ErrRecordNotFound) { + responseMap["response"] = "Key was not found on the server" + return false, responseMap + } + ipv6Addr := ipStruct.IPv6Address + boolRes, stringRes := manager.AddKey("wg0", ipv4Addr, ipv6Addr, pubKey, preKey) //add key to wg interface + if boolRes { + responseMap["response"] = "Enabled key successfully" + } else { + responseMap["response"] = stringRes + } + return boolRes, responseMap +} diff --git a/src/db/generatePeerArray.go b/src/db/generatePeerArray.go new file mode 100644 index 0000000..815d555 --- /dev/null +++ b/src/db/generatePeerArray.go @@ -0,0 +1,67 @@ +package db + +import ( + "errors" + "log" + "net" + "time" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/manager" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "gorm.io/gorm" +) + +func generatePeerArray() []wgtypes.PeerConfig { + var keyStruct []Key //key struct + var keyArray []wgtypes.PeerConfig //peers (clients) + db := DBSystem + + resultKey := db.Find(&keyStruct) + if errors.Is(resultKey.Error, gorm.ErrRecordNotFound) { + return keyArray + } else if resultKey.Error != nil { + log.Println("Error - Finding keys", resultKey.Error) + } + + for i := 0; i < len(keyStruct); i++ { //loop over all clients in db + var ipStruct IP + resultIP := db.Where("ipv4_address = ?", keyStruct[i].IPv4Address).First(&ipStruct) + if errors.Is(resultIP.Error, gorm.ErrRecordNotFound) { + log.Println("Cant find IPs", keyStruct[i].IPv4Address) + continue //continue even on error + } else if resultIP.Error != nil { + log.Println("Error - Finding IPs", keyStruct[i].IPv4Address, resultKey.Error) + } else if keyStruct[i].Enabled == "true" { //checks if key is enabled + pubKey, pubErr := manager.ParseKey(keyStruct[i].PublicKey) + preKey, preErr := manager.ParseKey(keyStruct[i].PresharedKey) + if pubErr != nil || preErr != nil { + log.Fatal("Error - Unable to parse keys on generate array") + } + + var ipAddresses []net.IPNet + ipv4, errIPv4 := manager.ParseIP(ipStruct.IPv4Address + "/32") + if errIPv4 != nil { + log.Fatal("Error - Parsing IPv4 Address", errIPv4) + } + ipAddresses = append(ipAddresses, *ipv4) + + if ipStruct.IPv6Address != "-" { + ipv6, errIPv6 := manager.ParseIP(ipStruct.IPv6Address + "/128") + if errIPv6 != nil { + log.Fatal("Error - Parsing IPv6 Address", errIPv6) + } + ipAddresses = append(ipAddresses, *ipv6) + } + + var zeroTime time.Duration + userConfig := wgtypes.PeerConfig{ + PublicKey: pubKey, + PresharedKey: &preKey, + PersistentKeepaliveInterval: &zeroTime, + AllowedIPs: ipAddresses, + } + keyArray = append(keyArray, userConfig) //add config to client array + } + } + return keyArray +} diff --git a/src/db/tableStructs.go b/src/db/tableStructs.go new file mode 100644 index 0000000..e525593 --- /dev/null +++ b/src/db/tableStructs.go @@ -0,0 +1,23 @@ +package db + +type Key struct { + KeyID int `gorm:"primaryKey;autoIncrement"` + PublicKey string `gorm:"unique"` + PresharedKey string `gorm:"unique"` + IPv4Address string `gorm:"foreignKey:IPv4Address"` + Enabled string +} +type IP struct { + IPv4Address string `gorm:"primaryKey"` + IPv6Address string `gorm:"unique"` + InUse string + WGInterface string +} +type WireguardInterface struct { + InterfaceName string `gorm:"primaryKey"` + PrivateKey string `gorm:"unique"` + PublicKey string `gorm:"unique"` + ListenPort int `gorm:"unique"` + IPv4Address string + IPv6Address string +} diff --git a/src/db/wgServerStartup.go b/src/db/wgServerStartup.go new file mode 100644 index 0000000..8b3906c --- /dev/null +++ b/src/db/wgServerStartup.go @@ -0,0 +1,42 @@ +package db + +import ( + "log" + "os" + + "gitlab.com/raspberry.tech/wireguard-manager-and-api/src/manager" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func WGStart() { + log.Println("Info - Starting up wg interface") + + var wgInterface WireguardInterface + db := DBSystem + result := db.Where("interface_name = ?", "wg0").First(&wgInterface) //find interface in sqlite db + + if result.Error != nil { //if an interface is not found, create one + pkServer, errPk := wgtypes.GeneratePrivateKey() + if errPk != nil { + log.Fatal("Error - Generating new private key", errPk) + } + + pubServer := pkServer.PublicKey() + + ipv4Addr := os.Getenv("WG_IPV4") + ipv6Addr := os.Getenv("WG_IPV6") + + if ipv6Addr != "-" { + createWG(pkServer.String(), pubServer.String(), 51820, ipv4Addr+"/16", ipv6Addr+"/64") + } else { + createWG(pkServer.String(), pubServer.String(), 51820, ipv4Addr+"/16", "-") + } + + peers := generatePeerArray() + manager.AddPeersInterface("wg0", pkServer.String(), 51820, peers) + return + } + + peers := generatePeerArray() + manager.AddPeersInterface("wg0", wgInterface.PrivateKey, wgInterface.ListenPort, peers) +}