2020-04-18 11:17:49 +02:00
package handler
import (
2020-04-18 16:42:53 +02:00
"encoding/base64"
2020-04-19 05:46:43 +02:00
"encoding/json"
2020-04-20 19:26:49 +02:00
"fmt"
2020-04-18 11:17:49 +02:00
"net/http"
"time"
"github.com/labstack/echo/v4"
2020-04-19 05:46:43 +02:00
"github.com/labstack/gommon/log"
2020-04-18 11:17:49 +02:00
"github.com/ngoduykhanh/wireguard-ui/model"
2020-04-18 16:42:53 +02:00
"github.com/ngoduykhanh/wireguard-ui/util"
2020-04-18 11:17:49 +02:00
"github.com/rs/xid"
2020-04-19 05:46:43 +02:00
"github.com/sdomino/scribble"
2020-04-18 16:42:53 +02:00
"github.com/skip2/go-qrcode"
2020-04-19 05:46:43 +02:00
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
2020-04-18 11:17:49 +02:00
)
2020-04-19 10:50:59 +02:00
// WireGuardClients handler
func WireGuardClients ( ) echo . HandlerFunc {
2020-04-18 11:17:49 +02:00
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 )
}
2020-04-20 05:36:20 +02:00
// read server information
serverInterface := model . ServerInterface { }
if err := db . Read ( "server" , "interfaces" , & serverInterface ) ; err != nil {
log . Error ( "Cannot fetch server interface config from database: " , err )
}
serverKeyPair := model . ServerKeypair { }
if err := db . Read ( "server" , "keypair" , & serverKeyPair ) ; err != nil {
log . Error ( "Cannot fetch server key pair from database: " , err )
}
// read global settings
globalSettings := model . GlobalSetting { }
if err := db . Read ( "server" , "global_settings" , & globalSettings ) ; err != nil {
log . Error ( "Cannot fetch global settings from database: " , err )
}
server := model . Server { }
server . Interface = & serverInterface
server . KeyPair = & serverKeyPair
// read client information and build a client list
2020-04-20 19:26:49 +02:00
records , err := db . ReadAll ( "clients" )
if err != nil {
log . Error ( "Cannot fetch clients from database: " , err )
}
2020-04-18 16:42:53 +02:00
clientDataList := [ ] model . ClientData { }
2020-04-18 11:17:49 +02:00
for _ , f := range records {
client := model . Client { }
2020-04-18 16:42:53 +02:00
clientData := model . ClientData { }
// get client info
2020-04-18 11:17:49 +02:00
if err := json . Unmarshal ( [ ] byte ( f ) , & client ) ; err != nil {
log . Error ( "Cannot decode client json structure: " , err )
}
2020-04-18 16:42:53 +02:00
clientData . Client = & client
// generate client qrcode image in base64
2020-04-20 05:36:20 +02:00
png , err := qrcode . Encode ( util . BuildClientConfig ( client , server , globalSettings ) , qrcode . Medium , 256 )
2020-04-18 16:42:53 +02:00
if err != nil {
log . Error ( "Cannot generate QRCode: " , err )
}
clientData . QRCode = "data:image/png;base64," + base64 . StdEncoding . EncodeToString ( [ ] byte ( png ) )
// create the list of clients and their qrcode data
clientDataList = append ( clientDataList , clientData )
2020-04-18 11:17:49 +02:00
}
2020-04-19 10:50:59 +02:00
return c . Render ( http . StatusOK , "clients.html" , map [ string ] interface { } {
2020-04-20 19:26:49 +02:00
"baseData" : model . BaseData { Active : "" } ,
2020-04-18 16:42:53 +02:00
"clientDataList" : clientDataList ,
2020-04-18 11:17:49 +02:00
} )
}
}
// NewClient handler
func NewClient ( ) echo . HandlerFunc {
2020-04-19 05:46:43 +02:00
return func ( c echo . Context ) error {
2020-04-18 11:17:49 +02:00
client := new ( model . Client )
c . Bind ( client )
2020-04-21 19:08:48 +02:00
// initialize db
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" } )
}
// read server information
serverInterface := model . ServerInterface { }
if err := db . Read ( "server" , "interfaces" , & serverInterface ) ; err != nil {
log . Error ( "Cannot fetch server interface config from database: " , err )
}
2020-04-20 19:26:49 +02:00
// validate the input Allocation IPs
2020-04-21 19:08:48 +02:00
allocatedIPs , err := util . GetAllocatedIPs ( )
check , err := util . ValidateIPAllocation ( serverInterface . Addresses , allocatedIPs , client . AllocatedIPs )
if ! check {
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , fmt . Sprintf ( "%s" , err ) } )
2020-04-20 19:26:49 +02:00
}
2020-04-19 05:46:43 +02:00
// validate the input AllowedIPs
if util . ValidateAllowedIPs ( client . AllowedIPs ) == false {
2020-04-19 10:50:59 +02:00
log . Warnf ( "Invalid Allowed IPs input from user: %v" , client . AllowedIPs )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , "Allowed IPs must be in CIDR format" } )
}
2020-04-18 11:17:49 +02:00
// gen ID
guid := xid . New ( )
client . ID = guid . String ( )
2020-04-19 19:15:25 +02:00
// gen Wireguard key pair
2020-04-18 11:17:49 +02:00
key , err := wgtypes . GeneratePrivateKey ( )
if err != nil {
2020-04-19 19:15:25 +02:00
log . Error ( "Cannot generate wireguard key pair: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot generate Wireguard key pair" } )
2020-04-18 11:17:49 +02:00
}
client . PrivateKey = key . String ( )
client . PublicKey = key . PublicKey ( ) . String ( )
client . CreatedAt = time . Now ( ) . UTC ( )
client . UpdatedAt = client . CreatedAt
2020-04-19 05:46:43 +02:00
// write client to the database
2020-04-18 11:17:49 +02:00
db . Write ( "clients" , client . ID , client )
log . Infof ( "Created wireguard client: %v" , client )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusOK , client )
2020-04-18 11:17:49 +02:00
}
}
// RemoveClient handler
func RemoveClient ( ) echo . HandlerFunc {
2020-04-19 05:46:43 +02:00
return func ( c echo . Context ) error {
2020-04-18 11:17:49 +02:00
client := new ( model . Client )
c . Bind ( client )
2020-04-19 05:46:43 +02:00
// delete client from database
2020-04-18 11:17:49 +02:00
dir := "./db"
db , err := scribble . New ( dir , nil )
if err != nil {
log . Error ( "Cannot initialize the database: " , err )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot access database" } )
2020-04-18 11:17:49 +02:00
}
if err := db . Delete ( "clients" , client . ID ) ; err != nil {
log . Error ( "Cannot delete wireguard client: " , err )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot delete client from database" } )
2020-04-18 11:17:49 +02:00
}
log . Infof ( "Removed wireguard client: %v" , client )
2020-04-19 05:46:43 +02:00
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Client removed" } )
2020-04-18 11:17:49 +02:00
}
2020-04-19 05:46:43 +02:00
}
2020-04-19 10:50:59 +02:00
// 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 )
}
2020-04-19 19:15:25 +02:00
serverKeyPair := model . ServerKeypair { }
if err := db . Read ( "server" , "keypair" , & serverKeyPair ) ; err != nil {
log . Error ( "Cannot fetch server key pair from database: " , err )
}
2020-04-19 10:50:59 +02:00
return c . Render ( http . StatusOK , "server.html" , map [ string ] interface { } {
2020-04-20 19:26:49 +02:00
"baseData" : model . BaseData { Active : "wg-server" } ,
2020-04-19 10:50:59 +02:00
"serverInterface" : serverInterface ,
2020-04-19 19:15:25 +02:00
"serverKeyPair" : serverKeyPair ,
2020-04-19 10:50:59 +02:00
} )
}
}
// 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 )
2020-04-19 19:19:00 +02:00
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , "Interface IP address must be in CIDR format" } )
2020-04-19 10:50:59 +02:00
}
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" } )
}
}
2020-04-19 19:15:25 +02:00
// WireGuardServerKeyPair handler to generate private and public keys
func WireGuardServerKeyPair ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
// gen Wireguard key pair
key , err := wgtypes . GeneratePrivateKey ( )
if err != nil {
log . Error ( "Cannot generate wireguard key pair: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot generate Wireguard key pair" } )
}
serverKeyPair := new ( model . ServerKeypair )
serverKeyPair . PrivateKey = key . String ( )
serverKeyPair . PublicKey = key . PublicKey ( ) . String ( )
serverKeyPair . 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" , "keypair" , serverKeyPair )
log . Infof ( "Updated wireguard server interfaces settings: %v" , serverKeyPair )
return c . JSON ( http . StatusOK , serverKeyPair )
}
}
2020-04-20 04:54:41 +02:00
// GlobalSettings handler
func GlobalSettings ( ) 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 )
}
globalSettings := model . GlobalSetting { }
if err := db . Read ( "server" , "global_settings" , & globalSettings ) ; err != nil {
log . Error ( "Cannot fetch global settings from database: " , err )
}
return c . Render ( http . StatusOK , "global_settings.html" , map [ string ] interface { } {
2020-04-20 19:26:49 +02:00
"baseData" : model . BaseData { Active : "global-settings" } ,
2020-04-20 04:54:41 +02:00
"globalSettings" : globalSettings ,
} )
}
}
// GlobalSettingSubmit handler to update the global settings
func GlobalSettingSubmit ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
globalSettings := new ( model . GlobalSetting )
c . Bind ( globalSettings )
// validate the input dns server list
if util . ValidateIPAddressList ( globalSettings . DNSServers ) == false {
log . Warnf ( "Invalid DNS server list input from user: %v" , globalSettings . DNSServers )
return c . JSON ( http . StatusBadRequest , jsonHTTPResponse { false , "Invalid DNS server address" } )
}
globalSettings . 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" , "global_settings" , globalSettings )
log . Infof ( "Updated global settings: %v" , globalSettings )
return c . JSON ( http . StatusOK , jsonHTTPResponse { true , "Updated global settings successfully" } )
}
}
2020-04-20 11:50:50 +02:00
// MachineIPAddresses handler to get local interface ip addresses
func MachineIPAddresses ( ) echo . HandlerFunc {
return func ( c echo . Context ) error {
// get private ip addresses
interfaceList , err := util . GetInterfaceIPs ( )
if err != nil {
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot get machine ip addresses" } )
}
// get public ip address
// TODO: Remove the go-external-ip dependency
publicInterface , err := util . GetPublicIP ( )
if err != nil {
log . Warn ( "Cannot get machine public ip address: " , err )
} else {
interfaceList = append ( interfaceList , publicInterface )
}
return c . JSON ( http . StatusOK , interfaceList )
}
}
2020-04-20 19:26:49 +02:00
// SuggestIPAllocation handler to get the list of ip address for client
func SuggestIPAllocation ( ) 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 )
}
// read server information
serverInterface := model . ServerInterface { }
if err := db . Read ( "server" , "interfaces" , & serverInterface ) ; err != nil {
log . Error ( "Cannot fetch server interface config from database: " , err )
}
// return the list of suggestedIPs
// we take the first available ip address from
// each server's network addresses.
suggestedIPs := make ( [ ] string , 0 )
allocatedIPs , err := util . GetAllocatedIPs ( )
if err != nil {
log . Error ( "Cannot suggest ip allocation. Failed to get list of allocated ip addresses: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , "Cannot suggest ip allocation: failed to get list of allocated ip addresses" } )
}
for _ , cidr := range serverInterface . Addresses {
ip , err := util . GetAvailableIP ( cidr , allocatedIPs )
if err != nil {
log . Error ( "Failed to get available ip from a CIDR: " , err )
return c . JSON ( http . StatusInternalServerError , jsonHTTPResponse { false , fmt . Sprintf ( "Cannot suggest ip allocation: failed to get available ip from network %s" , cidr ) } )
}
suggestedIPs = append ( suggestedIPs , fmt . Sprintf ( "%s/32" , ip ) )
}
return c . JSON ( http . StatusOK , suggestedIPs )
}
}